package mark2web

import (
	"io/ioutil"
	"os"
	"path"
	"regexp"
	"strings"
	"time"

	"gitbase.de/apairon/mark2web/pkg/helper"
	"github.com/davecgh/go-spew/spew"
	"github.com/extemporalgenome/slug"
	"github.com/flosch/pongo2"
	"gopkg.in/yaml.v2"
)

var CurrentContext *pongo2.Context
var CurrentTreeNode *TreeNode
var CurrentPathConfig *PathConfig

func NewContext() pongo2.Context {
	ctx := pongo2.Context{
		"fnRequest": RequestFn,
		"fnRender":  RenderFn,

		"AssetsPath": Config.Assets.ToPath,
		"Timestamp":  time.Now().Unix,
	}
	CurrentContext = &ctx

	return ctx
}

func (node *TreeNode) handleCollections() {
	for _, colConfig := range node.Config.This.Collections {
		if colConfig != nil {
			if colConfig.Name == nil || *colConfig.Name == "" {
				helper.Log.Panicf("missing Name in collection config in '%s'", node.InputPath)
			}
			if colConfig.URL == nil || *colConfig.URL == "" {
				helper.Log.Panicf("missing EntriesJSON in collection config in '%s'", node.InputPath)
			}
		}

		if node.ColMap == nil {
			node.ColMap = make(helper.MapString)
		}
		ctx := NewContext()
		ctx["This"] = node.Config.This
		ctx["Data"] = node.Config.Data

		url, err := pongo2.RenderTemplateString(*colConfig.URL, ctx)
		if err != nil {
			helper.Log.Panicf("invalid template string for Collection Element.URL in '%s': %s", node.InputPath, err)
		}

		colData := helper.JSONWebRequest(url)
		node.ColMap[*colConfig.Name] = colData

		if navT := colConfig.NavTemplate; navT != nil {
			var entries []interface{}
			var ok bool
			if navT.EntriesAttribute != "" {
				var colDataMap map[string]interface{}
				if colDataMap, ok = colData.(map[string]interface{}); ok {
					entries, ok = colDataMap[navT.EntriesAttribute].([]interface{})
					if !ok {
						helper.Log.Debug(spew.Sdump(colDataMap))
						helper.Log.Panicf("invalid json data in [%s] from url '%s' for entries", navT.EntriesAttribute, url)
					}
				}
			} else {
				entries, ok = colData.([]interface{})
			}
			if !ok {
				helper.Log.Debug(spew.Sdump(colData))
				helper.Log.Panicf("invalid json data from url '%s', need array of objects for entries or object with configured NavTemplate.EntriesAttribute", url)
			}

			// build navigation with detail sites
			for idx, colEl := range entries {
				ctxE := make(pongo2.Context)
				err := helper.Merge(&ctxE, ctx)
				if err != nil {
					helper.Log.Panicf("could not merge context in '%s': %s", node.InputPath, err)
				}
				var jsonCtx map[string]interface{}
				if jsonCtx, ok = colEl.(map[string]interface{}); !ok {
					helper.Log.Debug(spew.Sdump(colEl))
					helper.Log.Panicf("no json object for entry index %d from url '%s'", idx, url)
				}
				err = helper.Merge(&ctxE, pongo2.Context(jsonCtx))
				if err != nil {
					helper.Log.Panicf("could not merge context in '%s': %s", node.InputPath, err)
				}

				tpl := ""
				if navT.Template != "" {
					tpl, err = pongo2.RenderTemplateString(navT.Template, ctxE)
					if err != nil {
						helper.Log.Panicf("invalid template string for NavTemplate.Template in '%s': %s", node.InputPath, err)
					}
				}
				if tpl == "" {
					tpl = *node.Config.Template
				}

				dataKey := ""
				if navT.DataKey != "" {
					dataKey, err = pongo2.RenderTemplateString(navT.DataKey, ctxE)
					if err != nil {
						helper.Log.Panicf("invalid template string for NavTemplate.DataKey in '%s': %s", node.InputPath, err)
					}
				}

				goTo, err := pongo2.RenderTemplateString(navT.GoTo, ctxE)
				if err != nil {
					helper.Log.Panicf("invalid template string for NavTemplate.GoTo in '%s': %s", node.InputPath, err)
				}
				goTo = strings.Trim(goTo, "/")
				goTo = path.Clean(goTo)

				if strings.Contains(goTo, "..") {
					helper.Log.Panicf("going back via .. in NavTemplate.GoTo forbidden in collection config in '%s': %s", node.InputPath, goTo)
				}
				if goTo == "." {
					helper.Log.Panicf("invalid config '.' for NavTemplate.GoTo in collection config in '%s'", node.InputPath)
				}
				if goTo == "" {
					helper.Log.Panicf("missing NavTemplate.GoTo in collection config in '%s'", node.InputPath)
				}

				navname := ""
				if navT.Navname != "" {
					navname, err = pongo2.RenderTemplateString(navT.Navname, ctxE)
					if err != nil {
						helper.Log.Panicf("invalid template string for NavTemplate.Navname in '%s': %s", node.InputPath, err)
					}
				}
				body := ""
				if navT.Body != "" {
					body, err = pongo2.RenderTemplateString(navT.Body, ctxE)
					if err != nil {
						helper.Log.Panicf("invalid template string for NavTemplate.Body in '%s': %s", node.InputPath, err)
					}
				}

				Add2Nav(node, node.Config, tpl, goTo, navname, colEl, dataKey, body, navT.Hidden)
			}
		}
	}

}

func (node *TreeNode) fillConfig(inBase, outBase, dir string, conf *PathConfig) {
	inPath := inBase
	if dir != "" {
		inPath += "/" + dir
	}

	helper.Log.Infof("reading input directory: %s", inPath)

	node.InputPath = inPath

	// read config
	newConfig := new(PathConfig)
	helper.Log.Debug("looking for config.yml ...")
	configFile := inPath + "/config.yml"
	if _, err := os.Stat(configFile); os.IsNotExist(err) {
		helper.Log.Debug("no config.yml found in this directory, using upper configs")
		helper.Merge(newConfig, conf)
		// remove this
		newConfig.This = ThisPathConfig{}
	} else {
		helper.Log.Debug("reading config...")
		data, err := ioutil.ReadFile(configFile)
		if err != nil {
			helper.Log.Panicf("could not read file '%s': %s", configFile, err)
		}
		err = yaml.Unmarshal(data, newConfig)
		if err != nil {
			helper.Log.Panicf("could not parse YAML file '%s': %s", configFile, err)
		}

		helper.Log.Debug("merging config with upper config")
		oldThis := newConfig.This
		helper.Merge(newConfig, conf)
		newConfig.This = oldThis

		helper.Log.Debug(spew.Sdump(newConfig))
	}

	node.Config = newConfig

	// calc outDir
	stripedDir := dir
	var regexStr *string
	if newConfig.Path != nil {
		regexStr = newConfig.Path.Strip
	}
	if regexStr != nil && *regexStr != "" {
		if regex, err := regexp.Compile(*regexStr); err != nil {
			helper.Log.Panicf("error compiling path.strip regex '%s' from '%s': %s", *regexStr, inBase+"/"+dir, err)
		} else {
			stripedDir = regex.ReplaceAllString(stripedDir, "$1")
		}
	}

	if node.Config.This.Navname == nil {
		navname := strings.Replace(stripedDir, "_", " ", -1)
		node.Config.This.Navname = &navname
	}

	stripedDir = slug.Slug(stripedDir)
	outPath := outBase + "/" + stripedDir
	outPath = path.Clean(outPath)

	helper.Log.Infof("calculated output directory: %s", outPath)
	node.OutputPath = outPath

	// handle collections
	node.handleCollections()
}

func Add2Nav(currentNode *TreeNode, pathConfig *PathConfig, tplFilename, outDir string, navname string, ctx interface{}, dataMapKey string, body string, hidden bool) {
	newNode := new(TreeNode)
	newNode.root = currentNode.root
	newNode.fillConfig(
		currentNode.InputPath,
		currentNode.OutputPath,
		outDir,
		pathConfig,
	)
	if navname != "" {
		newNode.Config.This = ThisPathConfig{
			Navname: &navname,
		}
	}
	if dataMapKey != "" {
		if newNode.Config.Data == nil {
			newNode.Config.Data = make(helper.MapString)
		}
		// as submap in Data
		newNode.Config.Data[dataMapKey] = ctx
	} else if m, ok := ctx.(map[string]interface{}); ok {
		// direct set data
		newNode.Config.Data = m
	}

	// fake via normal file behavior
	newNode.Config.Template = &tplFilename
	newNode.InputFiles = []string{""} // empty file is special for use InputString
	indexInFile := ""
	indexOutFile := "index.html"
	if idx := newNode.Config.Index; idx != nil {
		if idx.OutputFile != nil && *idx.OutputFile != "" {
			indexOutFile = *idx.OutputFile
		}
	}
	newNode.Config.Index = &IndexConfig{
		InputFile:   &indexInFile,
		OutputFile:  &indexOutFile,
		InputString: &body,
	}
	newNode.Hidden = hidden

	currentNode.Sub = append(currentNode.Sub, newNode)
}
