// Copyright 2023 schukai GmbH
// SPDX-License-Identifier: AGPL-3.0

package document

import (
	"fmt"
	"gitlab.schukai.com/oss/utilities/documentation-manager/environment"
	"gitlab.schukai.com/oss/utilities/documentation-manager/translations"
	"gitlab.schukai.com/oss/utilities/documentation-manager/utils"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"regexp"
	"strings"
	"text/template"
	"time"
)

type BuildPdfEnvironment struct {
	SourcePath string
	DateFormat string
	OutputPath string
	Verbose    bool
	Templates  struct {
		Latex    string
		Markdown string
	}
}

func (t *BuildPdfEnvironment) GetComponentsTemplates() map[string]string {
	return map[string]string{}
}

type PdfDataset struct {
	Documents     string
	CreatedFormat string
}

func NewPdfDataset(env BuildPdfEnvironment) (*PdfDataset, error) {

	files, err := getFiles(&env, env.SourcePath)
	if err != nil {
		return nil, err
	}

	mapFiles, keys := buildFileMap(files)

	d := &PdfDataset{}

	docs := []string{}
	for _, key := range keys {

		text := mapFiles[key].textMeta.text

		text, s1Map := utils.MaskCodeBlocks(text, mapFiles[key].relSourcePath, 3)
		text, s2Map := utils.MaskCodeBlocks(text, mapFiles[key].relSourcePath, 1)

		text = convertHeadlines(text, mapFiles[key].level, mapFiles[key].textMeta.meta.Level)
		text = convertAwesomeBoxesToLatex(text)
		text = convertImages(text, mapFiles[key].baseDir)
		text = convertCircledNumbersToLatex(text)
		text = replaceKbdToLatex(text)
		text = replaceRelativeLinksToLatex(text, mapFiles[key], mapFiles)

		text = utils.InsertCodeBlocks(text, s2Map)
		text = utils.InsertCodeBlocks(text, s1Map)

		docs = append(docs, "\\newpage{}"+text)
	}

	d.Documents = strings.Join(docs, "\n")

	format := environment.State.GetDocumentDateFormat("")
	now := time.Now()
	d.CreatedFormat = now.Format(format)

	if env.Verbose {
		fmt.Println(d.Documents)
	}

	return d, nil
}

func BuildPDF(env BuildPdfEnvironment) error {

	output := env.OutputPath

	if env.DateFormat != "" {
		dateFormat = env.DateFormat
	}

	if !filepath.IsAbs(output) {
		pwd := os.Getenv("PWD")
		output = path.Clean(path.Join(pwd, output))
	}

	if output == "" {
		environment.ExitWithError(2, "the output option must be specified")
	}

	fileInfo, err := os.Stat(output)
	if err != nil {
		if !os.IsNotExist(err) {
			environment.ExitWithError(2, "cannot stat output file", err.Error())
		}
	} else {
		if fileInfo.IsDir() {
			environment.ExitWithError(2, "output file is a directory", output)
		}
	}

	file, err := ioutil.TempFile(os.TempDir(), environment.State.GetInfo().Mnemonic)
	checkError(err)

	defer func() {
		file.Close()
		os.Remove(file.Name())
	}()

	content := environment.ReadTemplate(env.Templates.Markdown)
	t, err := template.New("pdf").Funcs(getFuncMap(&env)).Parse(content)
	checkError(err)

	d, err := NewPdfDataset(env)
	checkError(err)

	err = t.Execute(file, d)
	checkError(err)

	luaFilter := createLuaFile()
	defer func() {
		if luaFilter != nil {
			luaFilter.Close()
			os.Remove(luaFilter.Name())
		}
	}()

	runPandoc(file.Name(), output, env.Templates.Latex, luaFilter.Name(), env.Verbose)
	return nil

}

func replaceRelativeLinksToLatex(content string, f *SourceFile, fileMap SourceFileMap) string {

	label := "link_" + f.hash
	content = "\\hypertarget{" + label + "}{ } \n" + strings.TrimSpace(content) + "\n"

	regEx := regexp.MustCompile(`(?:^|[^!])(?P<match>\[(?P<label>[^]]*)\]\((?P<path>[^)]*)\))`)
	matches := regEx.FindAllStringSubmatch(content, -1)
	if matches == nil {
		return content
	}

	for _, match := range matches {
		result := make(map[string]string)
		for i, name := range regEx.SubexpNames() {
			if i != 0 && name != "" {
				result[name] = match[i]
			}
		}

		if filepath.IsAbs(result["path"]) {
			//environment.State.AddWarning(translations.T.Sprintf("Absolute path not found: %s", result["path"]))
			continue
		}

		if utils.IsUrl(result["path"]) {
			continue
		}

		d := filepath.Dir(f.relSourcePath)
		p := filepath.Join(d, result["path"])

		p = strings.Split(p, "#")[0]

		ext := filepath.Ext(p)
		if ext == "" {
			environment.State.AddWarning(translations.T.Sprintf("No extension, of the link %s, in the file %s is not supported.", p, f.absSourcePath))
			continue
		}

		if ext != ".md" && ext != ".markdown" {
			environment.State.AddWarning(translations.T.Sprintf("The extension %s, of the link %s, in the file %s is not supported.", ext, p, f.absSourcePath))
			continue
		}

		s := fileMap.findByRelativePath(p)
		if s == nil {
			environment.State.AddWarning(translations.T.Sprintf("relative path %s, in file %s, cannot be resolved", result["path"], f.absSourcePath))
			continue
		}

		replace := "\\hyperlink{link_" + s.hash + "}{" + escapeLatexSpecialChars(result["label"]) + "}"
		content = strings.Replace(content, result["match"], replace, -1)

	}

	return content
}

func createLuaFile() *os.File {

	tmp, err := ioutil.TempFile(os.TempDir(), "lua-filter")
	if err != nil {
		environment.ExitWithError(2, "A temporary file cannot be created", err.Error())
	}

	tmp.WriteString(`
function RawBlock (raw)
	return raw.format:match "html"
	and pandoc.read(raw.text, "html").blocks
	or raw
end
`)

	return tmp

}

// https://ftp.gwdg.de/pub/ctan/graphics/awesomebox/awesomebox.pdf
func convertAwesomeBoxesToLatex(content string) string {

	regEx := regexp.MustCompile(`(?m)(?P<matches>!!!\s*(?P<type>[^\s]+)\s+?(?P<title>[^\n]*)\n(?P<lines>(?P<lastline>[[:blank:]]+[^\n]+\n)+))`)

	matches := regEx.FindAllStringSubmatch(content, -1)
	if matches == nil {
		return content
	}

	for _, match := range matches {
		result := make(map[string]string)
		for i, name := range regEx.SubexpNames() {
			if i != 0 && name != "" {
				result[name] = match[i]
			}
		}

		boxtype := "note"

		switch {
		case utils.Contains([]string{"notebox", "note", "info"}, result["type"]):
			boxtype = "note"
		case utils.Contains([]string{"tipbox", "tip", "hint"}, result["type"]):
			boxtype = "tip"

		case utils.Contains([]string{"warningbox", "warning", "warn"}, result["type"]):
			boxtype = "warning"

		case utils.Contains([]string{"cautionbox", "caution", "danger"}, result["type"]):
			boxtype = "caution"

		case utils.Contains([]string{"importantbox", "important"}, result["type"]):
			boxtype = "important"
		}

		c := ""

		t := escapeLatexSpecialChars(result["title"])
		if t != "" {
			c += "\\textbf{" + utils.TrimQuotes(t) + "}\n"
		}

		lines := result["lines"]
		lines = convertAwesomeBoxesMarkdownWithPandoc(lines)

		c += "\n" + lines + "\n" //+escapeLatexSpecialChars(result["lastline"])) + "\n"
		awesomebox := `\begin{` + boxtype + `block}` + c + "\n" + `\end{` + boxtype + `block}`

		content = strings.Replace(content, result["matches"], "\n"+awesomebox+"\n", 1)

	}

	return content

}

func convertAwesomeBoxesMarkdownWithPandoc(content string) string {
	output := runInlinePandoc(escapeLatexSpecialChars(utils.TrimLines(content)))
	return output
}

// The following characters play a special role in LaTeX and are called special printing characters, or simply special characters.
//
//	# $ % & ~ _ ^ \ { }
//
// Whenever you put one of these special characters into your file, you are doing something special. If you simply want the character
// to be printed just as any other letter, include a \ in front of the character. For example, \$ will produce $ in your output.
//
// The exception to the rule is the \ itself because \\ has its own special meaning. A \ is produced by typing $\backslash$ in your file.
//
// The meaning of these characters are:
//
// ~ (tilde) unbreakable space, use it whenever you want to leave a space which is unbreakable, and cannot expand or shrink, as e.q. in names: A.~U.~Thor.
// $ (dollar sign) to start an finish math mode.
// _ (underscore) for subscripts in math mode.
// ^ (hat) for superscripts in math mode.
// \ (backslash) starting commands, which extend until the first non-alphanumerical character. The space following the command is swallowed. The following line results in what expected:
//
//	The \TeX nician is an expert in \TeX{} language.
//
// {} (curly brackets) to group and separate commands from its surroundings. Must appear in pairs.
func escapeLatexSpecialChars(content string) string {

	result := ""

	runes := []rune(content)
	for i, k := range runes {

		if k == '\\' {
			result += "\\textbackslash{}"
			continue
		} else if utils.Contains([]string{"#", "$", "%", "&", "~", "_", "^", "\\", "{", "}"}, string(k)) {
			if i == 0 || runes[i-1] != '\\' {
				result += "\\"
			}

		}

		result += string(k)

	}

	return result

}