package mark2web

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

	"gitbase.de/apairon/mark2web/pkg/progress"

	"gitbase.de/apairon/mark2web/pkg/helper"
	"gitbase.de/apairon/mark2web/pkg/logger"
	"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) {
	progress.IncrTotal("content dir")
	progress.DescribeCurrent("content dir", "found "+inBase)

	if node.root == nil {
		// first node is root
		node.root = node
	}
	node.fillConfig(inBase, outBase, dir, conf)

	files, err := ioutil.ReadDir(node.InputPath)
	logger.Eexit(err, "could not read dir '%s'", node.InputPath)

	// 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":
				logger.D(".MD %s", p)
				if node.InputFiles == nil {
					node.InputFiles = make([]string, 0)
				}
				node.InputFiles = append(node.InputFiles, f.Name())
				break
			default:
				logger.D("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() {
			logger.D("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)
		}
	}
}

func (node *TreeNode) processMarkdownWithHeader(md []byte, errorRef string) (*PathConfig, *pongo2.Context) {

	newConfig := new(PathConfig)

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

		logger.D("found yaml header in '%s', merging config", errorRef)
		err := yaml.Unmarshal(yamlData, newConfig)
		logger.Eexit(err, "could not parse YAML header from '%s'", errorRef)

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

		logger.D(spew.Sdump(newConfig))

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

	// use --- for splitting document in markdown parts
	regex := regexp.MustCompile("\\r?\\n\\r?---\\r?\\n\\r?")
	inputParts := regex.Split(string(md), -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)
	node.buildNavigation(&navMap, &navSlice, &navActive)

	// read yaml header as data for template
	ctx := NewContext()
	ctx["This"] = newConfig.This
	ctx["Meta"] = newConfig.Meta
	ctx["Markdown"] = newConfig.Markdown
	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(md, chromaRenderer, chromaStyle)))
	ctx["BodyParts"] = htmlParts
	ctx["CurrentPath"] = node.CurrentNavPath()
	// 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:     node.BackToRootPath(),
			Active:   true,
			ColMap:   node.ColMap,
			Data:     node.Config.Data,
			This:     node.Config.This,
			SubMap:   &navMap,
			SubSlice: &navSlice,
		}
	}

	return newConfig, &ctx
}

// ProcessContent walks recursivly through the input paths and processes all files for output
func (node *TreeNode) ProcessContent() {
	progress.DescribeCurrent("content dir", "processing "+node.InputPath)

	helper.CreateDirectory(node.OutputPath)

	if node.root != node {
		// write htaccess for rewrites, root will be written in WriteWebserverConfig()
		goTo := node.Config.This.GoTo
		if goTo != nil && *goTo != "" {
			goToFixed := *goTo
			if strings.HasPrefix(goToFixed, "/") {
				goToFixed = node.BackToRootPath() + goToFixed
			}
			goToFixed = path.Clean(goToFixed)

			htaccessRedirect(node.OutputPath, goToFixed)
		}
	}

	for _, file := range node.InputFiles {
		inFile := "InputString"

		// ignore ???
		ignoreFile := false
		if file != "" {
			inFile = node.InputPath + "/" + file
			var ignoreRegex *string
			if f := node.Config.Filename; f != nil {
				ignoreRegex = f.Ignore
			}
			if ignoreRegex != nil && *ignoreRegex != "" {
				regex, err := regexp.Compile(*ignoreRegex)
				logger.Eexit(err, "could not compile filename.ignore regexp '%s' for file '%s'", *ignoreRegex, inFile)
				ignoreFile = regex.MatchString(file)
			}
		}

		if ignoreFile {
			logger.I("ignoring file '%s', because of filename.ignore", inFile)
		} else {
			var input []byte

			if file != "" {
				logger.D("reading file: %s", inFile)

				var err error
				input, err = ioutil.ReadFile(inFile)
				logger.Eexit(err, "could not read '%s'", inFile)

				logger.I("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 {
					logger.D("using input string instead of file")
					input = []byte(*inputString)
				}
			}

			newConfig, ctx := node.processMarkdownWithHeader(input, inFile)

			// build output filename
			outputFilename := file

			var stripRegex *string
			var outputExt *string
			if f := newConfig.Filename; f != nil {
				stripRegex = f.Strip
				outputExt = f.OutputExtension
			}

			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)
					logger.Eexit(err, "could not compile filename.strip regexp '%s' for file '%s'", *stripRegex, inFile)

					outputFilename = regex.ReplaceAllString(outputFilename, "$1")
				}
				if outputExt != nil && *outputExt != "" {
					outputFilename += "." + *outputExt
				}
			}

			outFile := node.OutputPath + "/" + outputFilename
			logger.D("using '%s' as output file", outFile)
			logger.D("rendering template '%s' for '%s'", *newConfig.Template, outFile)
			templateFilename := *newConfig.Template
			result, err := renderTemplate(*newConfig.Template, node, newConfig, ctx)
			logger.Eexit(err, "could not execute template '%s' for input file '%s': %s", templateFilename, inFile)

			result = node.fixAssetsPath(result)

			logger.N("writing to output file: %s", outFile)
			err = ioutil.WriteFile(outFile, []byte(result), 0644)
			logger.Eexit(err, "could not write to output file '%s'", outFile)

			handleCompression(outFile, []byte(result))

			//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
			logger.N("copying file from '%s' to '%s'", from, to)
			err := cpy.Copy(from, to)
			logger.Eexit(err, "could not copy file from '%s' to '%s': %s", from, to)

			handleCompression(to, nil)
		}
	}

	progress.IncrDone("content dir")

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