package render

import (
	"regexp"
	"strings"
)

type templateArgs = map[string]string

// singleRenderFunc processes a template string with the given args.
// For rendering like: "1234{{!arg}}6789" => "123456789"
//   - modified = true
//   - modifiedFromPos = 4
//   - modifiedContent = "56789"
type singleRenderFunc func(in string, args templateArgs) (modified bool, modifiedFromPos int, modifiedContent string)

type singleRender interface {
	render(in string, args templateArgs) (modified bool, modifiedFromPos int, modifiedContent string)
}

type templateRender struct {
	renders []singleRender
}

func newTemplateRender() *templateRender {
	return &templateRender{}
}

func (r *templateRender) registerRender(sr singleRender) {
	r.renders = append(r.renders, sr)
}

func (r *templateRender) doRender(in string, args templateArgs) string {
	for _, sr := range r.renders {
		in = renderBySingleRenderFunc(sr.render, in, args)
	}
	return in
}

func renderBySingleRenderFunc(fn singleRenderFunc, in string, args templateArgs) (res string) {
	var modified bool
	var modifiedFromPos int
	var out string
	for {
		modified, modifiedFromPos, out = fn(in, args)
		if !modified {
			res += in
			break
		}
		res += in[0:modifiedFromPos]
		in = out
	}
	return res
}

// removeUnexistedArgBlockRender removes blocks wrapped in {{!exist begin!key}} ... {{!exist end!key}}
// when the given key is not present in the args.
type removeUnexistedArgBlockRender struct {
	beginPrefixReg *regexp.Regexp
	argNameReg     *regexp.Regexp
	endPrefixReg   string
	argNameEndReg  string
}

func newRemoveUnexistedArgBlockRender() *removeUnexistedArgBlockRender {
	argNameEndReg := `\s*\}\}\n*`
	return &removeUnexistedArgBlockRender{
		beginPrefixReg: regexp.MustCompile(`[\t ]*\{\{\s*!\s*exist\s*begin\s*!\s*`),
		argNameReg:     regexp.MustCompile(`[a-zA-Z0-9._-]+` + argNameEndReg),
		endPrefixReg:   `[\t ]*\{\{\s*!\s*exist\s*end\s*!\s*`,
		argNameEndReg:  argNameEndReg,
	}
}

func regexFindStringIndex(r *regexp.Regexp, in string) (beginPos, charCnt int) {
	pair := r.FindStringIndex(in)
	if pair == nil {
		return -1, 0
	}
	return pair[0], pair[1] - pair[0]
}

func (remover *removeUnexistedArgBlockRender) render(in string, args templateArgs) (bool, int, string) {
	i, iCnt := regexFindStringIndex(remover.beginPrefixReg, in)
	if i < 0 {
		return false, 0, in
	}

	j, jCnt := regexFindStringIndex(remover.argNameReg, in[i+iCnt:])
	if j != 0 {
		return true, i + iCnt, in[i+iCnt:]
	}

	arg := strings.TrimRight(in[i+iCnt+j:i+iCnt+j+jCnt], "\t }\n")
	_, argFound := args[arg]

	endReg := regexp.MustCompile(remover.endPrefixReg + arg + remover.argNameEndReg)
	n, nCnt := regexFindStringIndex(endReg, in[i+iCnt+j+jCnt:])
	if n < 0 {
		return true, i + iCnt + j + jCnt, in[i+iCnt+j+jCnt:]
	}
	content := in[i+iCnt+j+jCnt : i+iCnt+j+jCnt+n]
	tail := in[i+iCnt+j+jCnt+n+nCnt:]
	if argFound {
		return true, i, content + tail
	} else {
		return true, i, tail
	}
}

// replaceArgByValRender replaces {{!key}} with the value from args.
type replaceArgByValRender struct {
	prefixReg  *regexp.Regexp
	argNameReg *regexp.Regexp
	suffixReg  *regexp.Regexp
}

func newReplaceArgByValRender() *replaceArgByValRender {
	return &replaceArgByValRender{
		prefixReg:  regexp.MustCompile(`\{\{\s*!\s*`),
		argNameReg: regexp.MustCompile(`[a-zA-Z0-9._-]+`),
		suffixReg:  regexp.MustCompile(`\s*\}\}`),
	}
}

func (replacer *replaceArgByValRender) render(in string, args templateArgs) (bool, int, string) {
	i, iCnt := regexFindStringIndex(replacer.prefixReg, in)
	if i < 0 {
		return false, 0, in
	}
	j, jCnt := regexFindStringIndex(replacer.argNameReg, in[i+iCnt:])
	if j != 0 {
		return true, i + iCnt, in[i+iCnt:]
	}
	arg := strings.TrimRight(in[i+iCnt+j:i+iCnt+j+jCnt], "\t }")
	val, argFound := args[arg]
	if !argFound {
		return true, i + iCnt + j + jCnt, in[i+iCnt+j+jCnt:]
	}

	val = replacer.multiLineIndent(in, i, val)

	n, nCnt := regexFindStringIndex(replacer.suffixReg, in[i+iCnt+j+jCnt:])
	if n < 0 {
		return true, i + iCnt + j + jCnt, in[i+iCnt+j+jCnt:]
	}

	tail := in[i+iCnt+j+jCnt+n+nCnt:]
	return true, i, val + tail
}

func (replacer *replaceArgByValRender) multiLineIndent(in string, i int, val string) string {
	argLines := strings.Split(val, "\n")
	if len(argLines) < 1 {
		return val
	}

	indent := ""
	for t := i - 1; t >= 0; t-- {
		if in[t] != '\t' && in[t] != ' ' {
			break
		}
		indent += string(in[t])
	}

	for m := 1; m < len(argLines); m++ {
		argLines[m] = indent + argLines[m]
	}
	return strings.Join(argLines, "\n")
}

// unfoldTemplateRender replaces {{!template:name}} with the value from args[name].
type unfoldTemplateRender struct {
	prefixReg  *regexp.Regexp
	argNameReg *regexp.Regexp
	suffixReg  *regexp.Regexp
}

func newUnfoldTemplateRender() *unfoldTemplateRender {
	return &unfoldTemplateRender{
		prefixReg:  regexp.MustCompile(`\{\{\s*!\s*template\s*:\s*`),
		argNameReg: regexp.MustCompile(`[a-zA-Z0-9._-]+`),
		suffixReg:  regexp.MustCompile(`\s*\}\}\n*`),
	}
}

func (replacer *unfoldTemplateRender) render(in string, args templateArgs) (bool, int, string) {
	i, iCnt := regexFindStringIndex(replacer.prefixReg, in)
	if i < 0 {
		return false, 0, in
	}

	j, jCnt := regexFindStringIndex(replacer.argNameReg, in[i+iCnt:])
	if j != 0 {
		return true, i + iCnt, in[i+iCnt:]
	}
	arg := strings.TrimRight(in[i+iCnt+j:i+iCnt+j+jCnt], "\t }")
	val, argFound := args[arg]
	if !argFound {
		return true, i + iCnt + j + jCnt, in[i+iCnt+j+jCnt:]
	}

	n, nCnt := regexFindStringIndex(replacer.suffixReg, in[i+iCnt+j+jCnt:])
	if n < 0 {
		return true, i + iCnt + j + jCnt, in[i+iCnt+j+jCnt:]
	}

	tail := in[i+iCnt+j+jCnt+n+nCnt:]
	return true, i, val + tail
}
