package mark2web

import (
	"bytes"
	"io/ioutil"
	"path"
	"regexp"
	"strings"

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

// ReadContentDir walks through content directory and builds the tree of configurations
func (node *TreeNode) ReadContentDir(inBase string, outBase string, dir string, conf *PathConfig) {
	if node.root == nil {
		// first node is root
		node.root = node
	}
	node.fillConfig(inBase, outBase, dir, conf)

	files, err := ioutil.ReadDir(node.InputPath)
	if err != nil {
		helper.Log.Panic(err)
	}

	// first only files
	for _, f := range files {
		p := node.InputPath + "/" + f.Name()
		if !f.IsDir() && f.Name() != "config.yml" {
			switch path.Ext(f.Name()) {
			case ".md":
				helper.Log.Debugf(".MD %s", p)
				if node.InputFiles == nil {
					node.InputFiles = make([]string, 0)
				}
				node.InputFiles = append(node.InputFiles, f.Name())
				break
			default:
				helper.Log.Debugf("FIL %s", p)
				if node.OtherFiles == nil {
					node.OtherFiles = make([]string, 0)
				}
				node.OtherFiles = append(node.OtherFiles, f.Name())
			}
		}
	}

	// only directorys, needed config before
	for _, f := range files {
		p := node.InputPath + "/" + f.Name()
		if f.IsDir() {
			helper.Log.Debugf("DIR %s", p)
			newTree := new(TreeNode)
			newTree.root = node.root
			if node.Sub == nil {
				node.Sub = make([]*TreeNode, 0)
			}
			node.Sub = append(node.Sub, newTree)
			newTree.ReadContentDir(node.InputPath, node.OutputPath, f.Name(), node.Config)
		}
	}
}

// ProcessContent walks recursivly through the input paths and processes all files for output
func (node *TreeNode) ProcessContent() {
	helper.CreateDirectory(node.OutputPath)

	curNavPath := strings.TrimPrefix(node.OutputPath, Config.Directories.Output)
	curNavPath = strings.TrimPrefix(curNavPath, "/")
	curNavPath = path.Clean(curNavPath)
	if curNavPath == "." {
		curNavPath = ""
	}

	goTo := node.Config.This.GoTo
	if goTo != nil && *goTo != "" {
		goToFixed := *goTo
		if strings.HasPrefix(goToFixed, "/") {
			goToFixed = helper.BackToRoot(curNavPath) + goToFixed
		}
		goToFixed = path.Clean(goToFixed)

		switch Config.Webserver.Type {
		case "apache":
			htaccessFile := node.OutputPath + "/.htaccess"
			helper.Log.Noticef("writing '%s' with redirect to: %s", htaccessFile, goToFixed)
			err := ioutil.WriteFile(htaccessFile, []byte(`RewriteEngine on
RewriteRule ^$ %{REQUEST_URI}`+goToFixed+`/ [R,L]
`), 0644)
			if err != nil {
				helper.Log.Panicf("could not write '%s': %s", htaccessFile, err)
			}
			break
		}
	}

	for _, file := range node.InputFiles {
		var input []byte
		inFile := "InputString"

		if file != "" {
			inFile = node.InputPath + "/" + file
			helper.Log.Debugf("reading file: %s", inFile)

			var err error
			input, err = ioutil.ReadFile(inFile)
			if err != nil {
				helper.Log.Panicf("could not read '%s':%s", inFile, err)
			}
			helper.Log.Infof("processing input file '%s'", inFile)
		} else {
			// use input string if available and input filename == ""
			var inputString *string
			if i := node.Config.Index; i != nil {
				inputString = i.InputString
			}
			if inputString != nil {
				helper.Log.Debugf("using input string instead of file")
				input = []byte(*inputString)
			}
		}

		newConfig := new(PathConfig)

		regex := regexp.MustCompile("(?s)^---(.*?)\\r?\\n\\r?---\\r?\\n\\r?")
		yamlData := regex.Find(input)
		if string(yamlData) != "" {
			// replace tabs
			yamlData = bytes.Replace(yamlData, []byte("\t"), []byte("    "), -1)

			helper.Log.Debugf("found yaml header in '%s', merging config", inFile)
			err := yaml.Unmarshal(yamlData, newConfig)
			if err != nil {
				helper.Log.Panicf("could not parse YAML header from '%s': %s", inFile, err)
			}

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

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

			input = regex.ReplaceAll(input, []byte(""))
		} else {
			helper.Merge(newConfig, node.Config)
		}

		// ignore ???
		ignoreFile := false
		var ignoreRegex *string
		var stripRegex *string
		var outputExt *string
		if f := newConfig.Filename; f != nil {
			ignoreRegex = f.Ignore
			stripRegex = f.Strip
			outputExt = f.OutputExtension
		}
		if ignoreRegex != nil && *ignoreRegex != "" {
			regex, err := regexp.Compile(*ignoreRegex)
			if err != nil {
				helper.Log.Panicf("could not compile filename.ignore regexp '%s' for file '%s': %s", *ignoreRegex, inFile, err)
			}
			ignoreFile = regex.MatchString(file)
		}

		if ignoreFile {
			helper.Log.Infof("ignoring file '%s', because of filename.ignore", inFile)
		} else {

			// build output filename
			outputFilename := file

			var indexInputFile *string
			var indexOutputFile *string
			if i := newConfig.Index; i != nil {
				indexInputFile = i.InputFile
				indexOutputFile = i.OutputFile
			}

			if indexInputFile != nil &&
				*indexInputFile == file &&
				indexOutputFile != nil &&
				*indexOutputFile != "" {
				outputFilename = *indexOutputFile
			} else {
				if stripRegex != nil && *stripRegex != "" {
					regex, err := regexp.Compile(*stripRegex)
					if err != nil {
						helper.Log.Panicf("could not compile filename.strip regexp '%s' for file '%s': %s", *stripRegex, inFile, err)
					}
					outputFilename = regex.ReplaceAllString(outputFilename, "$1")
				}
				if outputExt != nil && *outputExt != "" {
					outputFilename += "." + *outputExt
				}
			}

			outFile := node.OutputPath + "/" + outputFilename
			helper.Log.Debugf("using '%s' as output file", outFile)

			// use --- for splitting document in markdown parts
			regex := regexp.MustCompile("\\r?\\n\\r?---\\r?\\n\\r?")
			inputParts := regex.Split(string(input), -1)
			htmlParts := make([]*pongo2.Value, 0)

			chromaRenderer := false
			chromaStyle := "monokai"
			if m := newConfig.Markdown; m != nil {
				if m.ChromaRenderer != nil && *m.ChromaRenderer {
					chromaRenderer = true
				}
				if m.ChromaStyle != nil && *m.ChromaStyle != "" {
					chromaStyle = *m.ChromaStyle
				}
			}
			for _, iPart := range inputParts {
				htmlParts = append(htmlParts,
					pongo2.AsSafeValue(
						string(helper.RenderMarkdown([]byte(iPart), chromaRenderer, chromaStyle))))
			}

			// build navigation
			navMap := make(map[string]*NavElement)
			navSlice := make([]*NavElement, 0)
			navActive := make([]*NavElement, 0)
			buildNavigation(node.root, &navMap, &navSlice, &navActive, curNavPath)

			// read yaml header as data for template
			ctx := NewContext()
			ctx["This"] = newConfig.This
			ctx["Meta"] = newConfig.Meta
			ctx["Data"] = newConfig.Data
			ctx["ColMap"] = node.root.ColMap // root as NavMap and NavSlice, for sub go to NavElement.ColMap
			ctx["NavMap"] = navMap
			ctx["NavSlice"] = navSlice
			ctx["NavActive"] = navActive
			ctx["Body"] = pongo2.AsSafeValue(string(helper.RenderMarkdown(input, chromaRenderer, chromaStyle)))
			ctx["BodyParts"] = htmlParts
			ctx["CurrentPath"] = curNavPath
			// set active nav element
			if len(navActive) > 0 {
				ctx["NavElement"] = navActive[len(navActive)-1]
			} else {
				// if no active path to content, we are in root dir
				ctx["NavElement"] = &NavElement{
					GoTo:     helper.BackToRoot(curNavPath),
					Active:   true,
					ColMap:   node.ColMap,
					Data:     node.Config.Data,
					This:     node.Config.This,
					SubMap:   &navMap,
					SubSlice: &navSlice,
				}
			}

			helper.Log.Debugf("rendering template '%s' for '%s'", *newConfig.Template, outFile)
			templateFilename := *newConfig.Template
			result, err := renderTemplate(*newConfig.Template, node, newConfig, &ctx)
			if err != nil {
				helper.Log.Panicf("could not execute template '%s' for input file '%s': %s", templateFilename, inFile, err)
			}

			result = fixAssetsPath(result, curNavPath)

			helper.Log.Noticef("writing to output file: %s", outFile)
			err = ioutil.WriteFile(outFile, []byte(result), 0644)
			if err != nil {
				helper.Log.Panicf("could not write to output file '%s': %s", outFile, err)
			}

			//fmt.Println(string(html))
		}
	}

	// process other files, copy...
	for _, file := range node.OtherFiles {
		switch Config.OtherFiles.Action {
		case "copy":
			from := node.InputPath + "/" + file
			to := node.OutputPath + "/" + file
			helper.Log.Noticef("copying file from '%s' to '%s'", from, to)
			err := cpy.Copy(from, to)
			if err != nil {
				helper.Log.Panicf("could not copy file from '%s' to '%s': %s", from, to, err)
			}
		}
	}

	i := 0
	// sub can dynamically increase, so no for range
	for i < len(node.Sub) {
		node.Sub[i].ProcessContent()
		i++
	}
}
