package render

import (
	"bytes"
	"encoding/json"
	"fmt"
	"html/template"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"strings"

	log "github.com/Sirupsen/logrus"
	"github.com/oxtoacart/bpool"
)

const (
	ContentType    = "Content-Type"
	ContentLength  = "Content-Length"
	ContentBinary  = "application/octet-stream"
	ContentText    = "text/plain"
	ContentJSON    = "application/json"
	ContentHTML    = "text/html"
	ContentXHTML   = "application/xhtml+xml"
	ContentXML     = "text/xml"
	defaultCharset = "UTF-8"
)

var (
	bufpool     *bpool.BufferPool = bpool.NewBufferPool(48)
	helperFuncs                   = template.FuncMap{
		"yield": func() (string, error) {
			return "", fmt.Errorf("yield called with no layout defined")
		},
		"current": func() (string, error) {
			return "", nil
		},
	}
	rootTemplate *template.Template

	Directory  string
	Funcs      []template.FuncMap
	Extensions []string
	Layout     string
)

type Render struct {
	req      *http.Request
	w        http.ResponseWriter
	template *template.Template
}

func getExt(s string) string {
	if strings.Index(s, ".") == -1 {
		return ""
	}
	return "." + strings.Join(strings.Split(s, ".")[1:], ".")
}

func compile() *template.Template {
	dir := Directory
	t := template.New(dir)
	// parse an initial template in case we don't have any
	template.Must(t.Parse("dbweb"))

	if e := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		r, err := filepath.Rel(dir, path)
		if err != nil {
			return err
		}

		ext := getExt(r)

		for _, extension := range Extensions {
			if ext == extension {
				buf, err := ioutil.ReadFile(path)
				if err != nil {
					return err
				}

				name := (r[0 : len(r)-len(ext)])
				tmpl := t.New(filepath.ToSlash(name))

				// add our funcmaps
				for _, funcs := range Funcs {
					tmpl.Funcs(funcs)
				}

				// Bomb out if parse fails. We don't want any silent server starts.
				template.Must(tmpl.Funcs(helperFuncs).Parse(string(buf)))
				break
			}
		}

		return nil
	}); e != nil {
		log.Panic(e)
	}
	return t
}
func InitRender() {
	rootTemplate = compile()
}
func NewRender(w http.ResponseWriter, r *http.Request) *Render {
	t, e := rootTemplate.Clone()
	if e != nil {
		log.Panic(e)
	}
	return &Render{
		template: t,
		req:      r,
		w:        w,
	}
}
func (r *Render) Redirect(nav string) {
	http.Redirect(r.w, r.req, nav, http.StatusFound)
}
func (r *Render) execute(name string, binding interface{}) (*bytes.Buffer, error) {
	buf := bufpool.Get()
	return buf, r.template.ExecuteTemplate(buf, name, binding)
}
func (r *Render) addYield(name string, binding interface{}) {
	funcs := template.FuncMap{
		"yield": func() (template.HTML, error) {
			buf, err := r.execute(name, binding)
			// return safe html here since we are rendering our own template
			return template.HTML(buf.String()), err
		},
		"current": func() (string, error) {
			return name, nil
		},
	}
	r.template.Funcs(funcs)
}
func (r *Render) Template() *template.Template {
	return r.template
}
func (r *Render) HTML(statuscode int, name string, binding interface{}, layout ...string) {
	// assign a layout if there is one
	if len(layout) > 0 && len(layout[0]) > 0 {
		r.addYield(name, binding)
		name = layout[0]
	} else if len(Layout) > 0 {
		r.addYield(name, binding)
		name = Layout
	}
	buf, err := r.execute(name, binding)
	defer bufpool.Put(buf)
	if err != nil {
		http.Error(r.w, err.Error(), http.StatusInternalServerError)
		return
	}

	// template rendered fine, write out the result
	r.w.Header().Set(ContentType, ContentHTML+";"+defaultCharset)
	r.w.WriteHeader(statuscode)
	io.Copy(r.w, buf)

}
func (r *Render) JSON(statuscode int, data interface{}) {
	var result []byte
	var err error
	result, err = json.Marshal(data)
	if err != nil {
		http.Error(r.w, err.Error(), 500)
		return
	}

	// json rendered fine, write out the result
	r.w.Header().Set(ContentType, ContentJSON+";"+defaultCharset)
	r.w.WriteHeader(statuscode)
	r.w.Write(result)
}
