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"
)

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

type PdfDataset struct {
	Documents string
}

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

	files, err := getFiles(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 = convertAwesomeBoxes(text)
		text = convertImages(text, mapFiles[key].baseDir)
		text = convertCircledNumbers(text)
		text = replaceKbd(text)
		text = replaceRelativeLinks(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")

	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, "if the type is pdf, the output option must be specified")
	}

	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").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())
		}
	}()

	c, _ := os.ReadFile(file.Name())

	os.WriteFile("/tmp/debug.txt", c, 0644)

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

}

func replaceRelativeLinks(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 != ".md" && ext != ".markdown" {
			environment.State.AddWarning(translations.T.Sprintf("file extension %s, in file %s, not supported for relative links", ext, 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

}

type foundedTitleInfoStruct struct {
	level int
	match string
	title string
}

type foundedTitleInfo []foundedTitleInfoStruct

func convertHeadlines(content string, level, startLevel int) string {
	d := 0
	info, smallestLevelInTheDoc := calculateLevel(content)
	if startLevel == 0 {
		d = level - smallestLevelInTheDoc
	} else {
		d = startLevel - smallestLevelInTheDoc
	}

	if d < 0 {
		d = 0
	}

	for _, i := range info {
		fill := strings.Repeat("#", d+i.level)
		nw := fill + " " + i.title + "\n"
		content = strings.Replace(content, i.match, nw, 1)
	}

	return content
}

func calculateLevel(content string) (foundedTitleInfo, int) {
	regEx := regexp.MustCompile(`(?m)^(?P<match>(?P<level>#+)\s+(?P<title>[^\n]*))`)
	matches := regEx.FindAllStringSubmatch(content, -1)
	info := foundedTitleInfo{}
	if matches == nil {
		return info, -1
	}

	smallestLevelInTheDoc := 999

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

		info = append(info, foundedTitleInfoStruct{
			level: len(result["level"]),
			match: result["match"],
			title: result["title"],
		})

		if len(result["level"]) < smallestLevelInTheDoc {
			smallestLevelInTheDoc = len(result["level"])
		}

	}

	return info, smallestLevelInTheDoc
}

// https://ftp.gwdg.de/pub/ctan/graphics/awesomebox/awesomebox.pdf
func convertAwesomeBoxes(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 = convertAwesomeBoxesMarkdown(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 convertAwesomeBoxesMarkdown(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

}

func convertImages(content string, absolute string) string {

	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 utils.IsUrl(result["path"]) {
			continue
		}

		if filepath.IsAbs(result["path"]) {
			continue
		}

		p := filepath.Join(absolute, result["path"])
		path := path.Clean(p)
		content = strings.Replace(content, result["match"], "!["+result["label"]+"]("+path+")", -1)

	}

	return content
}

//func convertTemplateLogo(content string, absolute string) string {
//	todoRegEx := regexp.MustCompile(`(?m)^(?P<match>logo:\s*"?(?P<path>[^"\n]*)"?\s*)$`)
//
//	matches := todoRegEx.FindAllStringSubmatch(content, -1)
//	if matches == nil {
//		return content
//	}
//
//	for _, match := range matches {
//		result := make(map[string]string)
//		for i, name := range todoRegEx.SubexpNames() {
//			if i != 0 && name != "" {
//				result[name] = match[i]
//			}
//		}
//
//		if filepath.IsAbs(result["path"]) {
//			continue
//		}
//
//		path := path.Clean(absolute + "/" + result["path"])
//		content = strings.Replace(content, result["match"], "logo: \""+path+"\"", -1)
//
//	}
//
//	return content
//}

//func convertTemplateLatexLogo(content string, absolute string) string {
//	todoRegEx := regexp.MustCompile(`(?m)(?P<match>\\includegraphics[^{]*\{(?P<path>[^}]*)\})`)
//
//	matches := todoRegEx.FindAllStringSubmatch(content, -1)
//	if matches == nil {
//		return content
//	}
//
//	for _, match := range matches {
//		result := make(map[string]string)
//		for i, name := range todoRegEx.SubexpNames() {
//			if i != 0 && name != "" {
//				result[name] = match[i]
//			}
//		}
//
//		if filepath.IsAbs(result["path"]) {
//			continue
//		}
//
//		path := path.Clean(absolute + "/" + result["path"])
//		a := strings.Replace(result["match"], result["path"], path, -1)
//
//		content = strings.Replace(content, result["match"], a, -1)
//
//	}
//
//	return content
//}

//
//func convertTemplateImages(content string, absolute string) string {
//	content = convertTemplateLogo(content, absolute)
//	content = convertTemplateLatexLogo(content, absolute)
//
//	return content
//
//}