package common

import (
	"fmt"
	"html/template"
	"io"
	"log"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/Azareal/Gosora/common/alerts"
	p "github.com/Azareal/Gosora/common/phrases"
	tmpl "github.com/Azareal/Gosora/common/templates"
	qgen "github.com/Azareal/Gosora/query_gen"
)

var Ctemplates []string // TODO: Use this to filter out top level templates we don't need
var DefaultTemplates = template.New("")
var DefaultTemplateFuncMap map[string]interface{}

//var Templates = template.New("")
var PrebuildTmplList []func(User, *Header) CTmpl

func skipCTmpl(key string) bool {
	for _, tmpl := range Ctemplates {
		if strings.HasSuffix(key, "/"+tmpl+".html") {
			return true
		}
	}
	return false
}

type CTmpl struct {
	Name       string
	Filename   string
	Path       string
	StructName string
	Data       interface{}
	Imports    []string
}

func genIntTmpl(name string) func(pi interface{}, w io.Writer) error {
	return func(pi interface{}, w io.Writer) error {
		theme := Themes[DefaultThemeBox.Load().(string)]
		mapping, ok := theme.TemplatesMap[name]
		if !ok {
			mapping = name
		}
		return DefaultTemplates.ExecuteTemplate(w, mapping+".html", pi)
	}
}

// TODO: Refactor the template trees to not need these
// nolint
var Template_topic_handle = genIntTmpl("topic")
var Template_topic_guest_handle = Template_topic_handle
var Template_topic_member_handle = Template_topic_handle
var Template_topic_alt_handle = genIntTmpl("topic")
var Template_topic_alt_guest_handle = Template_topic_alt_handle
var Template_topic_alt_member_handle = Template_topic_alt_handle

// nolint
var Template_topics_handle = genIntTmpl("topics")
var Template_topics_guest_handle = Template_topics_handle
var Template_topics_member_handle = Template_topics_handle

// nolint
var Template_forum_handle = genIntTmpl("forum")
var Template_forum_guest_handle = Template_forum_handle
var Template_forum_member_handle = Template_forum_handle

// nolint
var Template_forums_handle = genIntTmpl("forums")
var Template_forums_guest_handle = Template_forums_handle
var Template_forums_member_handle = Template_forums_handle

// nolint
var Template_profile_handle = genIntTmpl("profile")
var Template_profile_guest_handle = Template_profile_handle
var Template_profile_member_handle = Template_profile_handle

// nolint
var Template_create_topic_handle = genIntTmpl("create_topic")
var Template_login_handle = genIntTmpl("login")
var Template_register_handle = genIntTmpl("register")
var Template_error_handle = genIntTmpl("error")
var Template_ip_search_handle = genIntTmpl("ip_search")
var Template_account_handle = genIntTmpl("account")

func tmplInitUsers() (*User, *User, *User) {
	avatar, microAvatar := BuildAvatar(62, "")
	u := User{62, BuildProfileURL("fake-user", 62), "Fake User", "compiler@localhost", 0, false, false, false, false, false, false, GuestPerms, make(map[string]bool), "", false, "", avatar, microAvatar, "", "", 0, 0, 0, 0, StartTime, "0.0.0.0.0", 0, 0, nil}

	// TODO: Do a more accurate level calculation for this?
	avatar, microAvatar = BuildAvatar(1, "")
	u2 := User{1, BuildProfileURL("admin-alice", 1), "Admin Alice", "alice@localhost", 1, true, true, true, true, false, false, AllPerms, make(map[string]bool), "", true, "", avatar, microAvatar, "", "", 58, 1000, 0, 1000, StartTime, "127.0.0.1", 0, 0, nil}

	avatar, microAvatar = BuildAvatar(2, "")
	u3 := User{2, BuildProfileURL("admin-fred", 62), "Admin Fred", "fred@localhost", 1, true, true, true, true, false, false, AllPerms, make(map[string]bool), "", true, "", avatar, microAvatar, "", "", 42, 900, 0, 900, StartTime, "::1", 0, 0, nil}
	return &u, &u2, &u3
}

func tmplInitHeaders(user, user2, user3 *User) (*Header, *Header, *Header) {
	header := &Header{
		Site:            Site,
		Settings:        SettingBox.Load().(SettingMap),
		Themes:          Themes,
		Theme:           Themes[DefaultThemeBox.Load().(string)],
		CurrentUser:     user,
		NoticeList:      []string{"test"},
		Stylesheets:     []string{"panel.css"},
		Scripts:         []string{"whatever.js"},
		PreScriptsAsync: []string{"whatever.js"},
		ScriptsAsync:    []string{"whatever.js"},
		Widgets: PageWidgets{
			LeftSidebar: template.HTML("lalala"),
		},
	}

	buildHeader := func(user *User) *Header {
		head := &Header{Site: Site}
		*head = *header
		head.CurrentUser = user
		return head
	}

	return header, buildHeader(user2), buildHeader(user3)
}

type TmplLoggedin struct {
	Stub   string
	Guest  string
	Member string
}

type nobreak interface{}

type TItem struct {
	Expects    string
	ExpectsInt interface{}
	LoggedIn   bool
}

type TItemHold map[string]TItem

func (h TItemHold) Add(name, expects string, expectsInt interface{}) {
	h[name] = TItem{expects, expectsInt, true}
}

func (h TItemHold) AddStd(name, expects string, expectsInt interface{}) {
	h[name] = TItem{expects, expectsInt, false}
}

// ? - Add template hooks?
func CompileTemplates() error {
	log.Print("Compiling the templates")
	// TODO: Implement per-theme template overrides here too
	overriden := make(map[string]map[string]bool)
	for _, theme := range Themes {
		overriden[theme.Name] = make(map[string]bool)
		log.Printf("theme.OverridenTemplates: %+v\n", theme.OverridenTemplates)
		for _, override := range theme.OverridenTemplates {
			overriden[theme.Name][override] = true
		}
	}
	log.Printf("overriden: %+v\n", overriden)

	config := tmpl.CTemplateConfig{
		Minify:     Config.MinifyTemplates,
		Debug:      Dev.DebugMode,
		SuperDebug: Dev.TemplateDebug,
	}
	c := tmpl.NewCTemplateSet("normal")
	c.SetConfig(config)
	c.SetBaseImportMap(map[string]string{
		"io":                               "io",
		"github.com/Azareal/Gosora/common": "c github.com/Azareal/Gosora/common",
	})
	c.SetBuildTags("!no_templategen")
	c.SetOverrideTrack(overriden)
	c.SetPerThemeTmpls(make(map[string]bool))

	log.Print("Compiling the default templates")
	var wg sync.WaitGroup
	if err := compileTemplates(&wg, c, ""); err != nil {
		return err
	}
	oroots := c.GetOverridenRoots()
	log.Printf("oroots: %+v\n", oroots)

	log.Print("Compiling the per-theme templates")
	for theme, tmpls := range oroots {
		c.ResetLogs("normal-" + theme)
		c.SetThemeName(theme)
		c.SetPerThemeTmpls(tmpls)
		log.Print("theme: ", theme)
		log.Printf("perThemeTmpls: %+v\n", tmpls)
		err := compileTemplates(&wg, c, theme)
		if err != nil {
			return err
		}
	}
	writeTemplateList(c, &wg, "./")
	return nil
}

func compileCommons(c *tmpl.CTemplateSet, head, head2 *Header, forumList []Forum, o TItemHold) error {
	// TODO: Add support for interface{}s
	_, user2, user3 := tmplInitUsers()
	now := time.Now()

	// Convienience function to save a line here and there
	htitle := func(name string) *Header {
		head.Title = name
		return head
	}
	/*htitle2 := func(name string) *Header {
		head2.Title = name
		return head2
	}*/

	var topicsList []*TopicsRow
	topicsList = append(topicsList, &TopicsRow{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, now, now, user3.ID, 1, 1, "", "127.0.0.1", 1, 0, 1, 1, 0, "classname", 0, "", user2, "", 0, user3, "General", "/forum/general.2", nil})
	topicListPage := TopicListPage{htitle("Topic List"), topicsList, forumList, Config.DefaultForum, TopicListSort{"lastupdated", false}, Paginator{[]int{1}, 1, 1}}
	o.Add("topics", "c.TopicListPage", topicListPage)

	forumItem := BlankForum(1, "general-forum.1", "General Forum", "Where the general stuff happens", true, "all", 0, "", 0)
	forumPage := ForumPage{htitle("General Forum"), topicsList, forumItem, Paginator{[]int{1}, 1, 1}}
	o.Add("forum", "c.ForumPage", forumPage)
	o.Add("forums", "c.ForumsPage", ForumsPage{htitle("Forum List"), forumList})

	poll := Poll{ID: 1, Type: 0, Options: map[int]string{0: "Nothing", 1: "Something"}, Results: map[int]int{0: 5, 1: 2}, QuickOptions: []PollOption{
		PollOption{0, "Nothing"},
		PollOption{1, "Something"},
	}, VoteCount: 7}
	avatar, microAvatar := BuildAvatar(62, "")
	miniAttach := []*MiniAttachment{&MiniAttachment{Path: "/"}}
	topic := TopicUser{1, "blah", "Blah", "Hey there!", 0, false, false, now, now, 1, 1, 0, "", "127.0.0.1", 1, 0, 1, 0, "classname", poll.ID, "weird-data", BuildProfileURL("fake-user", 62), "Fake User", Config.DefaultGroup, avatar, microAvatar, 0, "", "", "", 58, false, miniAttach, nil, false}

	var replyList []*ReplyUser
	reply := Reply{1, 1, "Yo!", 1, Config.DefaultGroup, now, 0, 0, 1, "::1", true, 1, 1, ""}
	ru := &ReplyUser{ClassName: "", Reply: reply, CreatedByName: "Alice", Avatar: avatar, Level: 0, Attachments: miniAttach}
	ru.Init()
	replyList = append(replyList, ru)
	tpage := TopicPage{htitle("Topic Name"), replyList, topic, &Forum{ID: 1, Name: "Hahaha"}, poll, Paginator{[]int{1}, 1, 1}}
	tpage.Forum.Link = BuildForumURL(NameToSlug(tpage.Forum.Name), tpage.Forum.ID)
	o.Add("topic", "c.TopicPage", tpage)
	o.Add("topic_mini", "c.TopicPage", tpage)
	o.Add("topic_alt", "c.TopicPage", tpage)
	o.Add("topic_alt_mini", "c.TopicPage", tpage)
	return nil
}

func compileTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName string) error {
	// Schemas to train the template compiler on what to expect
	// TODO: Add support for interface{}s
	user, user2, user3 := tmplInitUsers()
	header, header2, _ := tmplInitHeaders(user, user2, user3)
	now := time.Now()

	/*poll := Poll{ID: 1, Type: 0, Options: map[int]string{0: "Nothing", 1: "Something"}, Results: map[int]int{0: 5, 1: 2}, QuickOptions: []PollOption{
		PollOption{0, "Nothing"},
		PollOption{1, "Something"},
	}, VoteCount: 7}*/
	//avatar, microAvatar := BuildAvatar(62, "")
	miniAttach := []*MiniAttachment{&MiniAttachment{Path: "/"}}
	var replyList []*ReplyUser
	//topic := TopicUser{1, "blah", "Blah", "Hey there!", 0, false, false, now, now, 1, 1, 0, "", "127.0.0.1", 1, 0, 1, 0, "classname", poll.ID, "weird-data", BuildProfileURL("fake-user", 62), "Fake User", Config.DefaultGroup, avatar, microAvatar, 0, "", "", "", "", "", 58, false, miniAttach, nil}
	// TODO: Do we want the UID on this to be 0?
	//avatar, microAvatar = BuildAvatar(0, "")
	reply := Reply{1, 1, "Yo!", 1, Config.DefaultGroup, now, 0, 0, 1, "::1", true, 1, 1, ""}
	ru := &ReplyUser{ClassName: "", Reply: reply, CreatedByName: "Alice", Avatar: "", Level: 0, Attachments: miniAttach}
	ru.Init()
	replyList = append(replyList, ru)

	// TODO: Use a dummy forum list to avoid o(n) problems
	var forumList []Forum
	forums, err := Forums.GetAll()
	if err != nil {
		return err
	}
	for _, forum := range forums {
		forumList = append(forumList, *forum)
	}

	// Convienience function to save a line here and there
	htitle := func(name string) *Header {
		header.Title = name
		return header
	}
	t := TItemHold(make(map[string]TItem))
	err = compileCommons(c, header, header2, forumList, t)
	if err != nil {
		return err
	}

	ppage := ProfilePage{htitle("User 526"), replyList, *user, 0, 0, false, false, false} // TODO: Use the score from user to generate the currentScore and nextScore
	t.Add("profile", "c.ProfilePage", ppage)

	var topicsList []*TopicsRow
	topicsList = append(topicsList, &TopicsRow{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, now, now, user3.ID, 1, 1, "", "127.0.0.1", 1, 0, 1, 1, 0, "classname", 0, "", user2, "", 0, user3, "General", "/forum/general.2", nil})
	topicListPage := TopicListPage{htitle("Topic List"), topicsList, forumList, Config.DefaultForum, TopicListSort{"lastupdated", false}, Paginator{[]int{1}, 1, 1}}

	forumItem := BlankForum(1, "general-forum.1", "General Forum", "Where the general stuff happens", true, "all", 0, "", 0)
	forumPage := ForumPage{htitle("General Forum"), topicsList, forumItem, Paginator{[]int{1}, 1, 1}}

	// Experimental!
	for _, tmpl := range strings.Split(Dev.ExtraTmpls, ",") {
		sp := strings.Split(tmpl, ":")
		if len(sp) < 2 {
			continue
		}
		typ := "0"
		if len(sp) == 3 {
			typ = sp[2]
		}

		var pi interface{}
		switch sp[1] {
		case "c.TopicListPage":
			pi = topicListPage
		case "c.ForumPage":
			pi = forumPage
		case "c.ProfilePage":
			pi = ppage
		case "c.Page":
			pi = Page{htitle("Something"), tList, nil}
		default:
			continue
		}

		if typ == "1" {
			t.Add(sp[0], sp[1], pi)
		} else {
			t.AddStd(sp[0], sp[1], pi)
		}
	}

	t.AddStd("login", "c.Page", Page{htitle("Login Page"), tList, nil})
	t.AddStd("register", "c.Page", Page{htitle("Registration Page"), tList, false})
	t.AddStd("error", "c.ErrorPage", ErrorPage{htitle("Error"), "A problem has occurred in the system."})

	ipSearchPage := IPSearchPage{htitle("IP Search"), map[int]*User{1: user2}, "::1"}
	t.AddStd("ip_search", "c.IPSearchPage", ipSearchPage)

	var inter nobreak
	accountPage := Account{header, "dashboard", "account_own_edit", inter}
	t.AddStd("account", "c.Account", accountPage)

	parti := []*User{user}
	convo := &Conversation{1, BuildConvoURL(1), user.ID, time.Now(), 0, time.Now()}
	convoItems := []ConvoViewRow{ConvoViewRow{&ConversationPost{1, 1, "hey", "", user.ID}, user, "", 4, true}}
	convoPage := ConvoViewPage{header, convo, convoItems, parti, true, Paginator{[]int{1}, 1, 1}}
	t.AddStd("convo", "c.ConvoViewPage", convoPage)

	convos := []*ConversationExtra{&ConversationExtra{&Conversation{}, []*User{user}}}
	var cRows []ConvoListRow
	for _, convo := range convos {
		cRows = append(cRows, ConvoListRow{convo, convo.Users, false})
	}
	convoListPage := ConvoListPage{header, cRows, Paginator{[]int{1}, 1, 1}}
	t.AddStd("convos", "c.ConvoListPage", convoListPage)

	basePage := &BasePanelPage{header, PanelStats{}, "dashboard", ReportForumID}
	t.AddStd("panel", "c.Panel", Panel{basePage, "panel_dashboard_right", "", "panel_dashboard", inter})
	ges := []GridElement{GridElement{"", "", "", 1, "grid_istat", "", "", ""}}
	t.AddStd("panel_dashboard", "c.DashGrids", DashGrids{ges, ges})

	goVersion := runtime.Version()
	dbVersion := qgen.Builder.DbVersion()
	var memStats runtime.MemStats
	runtime.ReadMemStats(&memStats)
	debugTasks := DebugPageTasks{0, 0, 0, 0, 0}
	debugCache := DebugPageCache{1, 1, 1, 2, 2, 2, true}
	debugDatabase := DebugPageDatabase{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
	debugDisk := DebugPageDisk{1, 1, 1, 1, 1, 1}
	dpage := PanelDebugPage{basePage, goVersion, dbVersion, "0s", 1, qgen.Builder.GetAdapter().GetName(), 1, 1, debugTasks, memStats, debugCache, debugDatabase, debugDisk}
	t.AddStd("panel_debug", "c.PanelDebugPage", dpage)
	//t.AddStd("panel_analytics", "c.PanelAnalytics", Panel{basePage, "panel_dashboard_right","panel_dashboard", inter})

	writeTemplate := func(name string, content interface{}) {
		log.Print("Writing template '" + name + "'")
		writeTmpl := func(name, content string) {
			if content == "" {
				return //log.Fatal("No content body for " + name)
			}
			err := writeFile("./template_"+name+".go", content)
			if err != nil {
				log.Fatal(err)
			}
		}
		wg.Add(1)
		go func() {
			tname := themeName
			if tname != "" {
				tname = "_" + tname
			}
			switch content := content.(type) {
			case string:
				writeTmpl(name+tname, content)
			case TmplLoggedin:
				writeTmpl(name+tname, content.Stub)
				writeTmpl(name+tname+"_guest", content.Guest)
				writeTmpl(name+tname+"_member", content.Member)
			}
			wg.Done()
		}()
	}

	// Let plugins register their own templates
	DebugLog("Registering the templates for the plugins")
	config := c.GetConfig()
	config.SkipHandles = true
	c.SetConfig(config)
	for _, tmplfunc := range PrebuildTmplList {
		tmplItem := tmplfunc(*user, header)
		varList := make(map[string]tmpl.VarItem)
		compiledTmpl, err := c.Compile(tmplItem.Filename, tmplItem.Path, tmplItem.StructName, tmplItem.Data, varList, tmplItem.Imports...)
		if err != nil {
			return err
		}
		writeTemplate(tmplItem.Name, compiledTmpl)
	}

	log.Print("Writing the templates")
	for name, titem := range t {
		log.Print("Writing " + name)
		varList := make(map[string]tmpl.VarItem)
		if titem.LoggedIn {
			stub, guest, member, err := c.CompileByLoggedin(name+".html", "templates/", titem.Expects, titem.ExpectsInt, varList)
			if err != nil {
				return err
			}
			writeTemplate(name, TmplLoggedin{stub, guest, member})
		} else {
			tmpl, err := c.Compile(name+".html", "templates/", titem.Expects, titem.ExpectsInt, varList)
			if err != nil {
				return err
			}
			writeTemplate(name, tmpl)
		}
	}

	return nil
}

// ? - Add template hooks?
func CompileJSTemplates() error {
	log.Print("Compiling the JS templates")
	// TODO: Implement per-theme template overrides here too
	overriden := make(map[string]map[string]bool)
	for _, theme := range Themes {
		overriden[theme.Name] = make(map[string]bool)
		log.Printf("theme.OverridenTemplates: %+v\n", theme.OverridenTemplates)
		for _, override := range theme.OverridenTemplates {
			overriden[theme.Name][override] = true
		}
	}
	log.Printf("overriden: %+v\n", overriden)

	config := tmpl.CTemplateConfig{
		Minify:         Config.MinifyTemplates,
		Debug:          Dev.DebugMode,
		SuperDebug:     Dev.TemplateDebug,
		SkipHandles:    true,
		SkipTmplPtrMap: true,
		SkipInitBlock:  false,
		PackageName:    "tmpl",
	}
	c := tmpl.NewCTemplateSet("js")
	c.SetConfig(config)
	c.SetBuildTags("!no_templategen")
	c.SetOverrideTrack(overriden)
	c.SetPerThemeTmpls(make(map[string]bool))

	log.Print("Compiling the default templates")
	var wg sync.WaitGroup
	err := compileJSTemplates(&wg, c, "")
	if err != nil {
		return err
	}
	oroots := c.GetOverridenRoots()
	log.Printf("oroots: %+v\n", oroots)

	log.Print("Compiling the per-theme templates")
	for theme, tmpls := range oroots {
		c.SetThemeName(theme)
		c.SetPerThemeTmpls(tmpls)
		log.Print("theme: ", theme)
		log.Printf("perThemeTmpls: %+v\n", tmpls)
		err = compileJSTemplates(&wg, c, theme)
		if err != nil {
			return err
		}
	}
	dirPrefix := "./tmpl_client/"
	writeTemplateList(c, &wg, dirPrefix)
	return nil
}

func compileJSTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName string) error {
	user, user2, user3 := tmplInitUsers()
	header, _, _ := tmplInitHeaders(user, user2, user3)
	now := time.Now()
	varList := make(map[string]tmpl.VarItem)

	c.SetBaseImportMap(map[string]string{
		"io": "io",
		"github.com/Azareal/Gosora/common/alerts": "github.com/Azareal/Gosora/common/alerts",
	})

	// TODO: Check what sort of path is sent exactly and use it here
	alertItem := alerts.AlertItem{Avatar: "", ASID: 1, Path: "/", Message: "uh oh, something happened"}
	alertTmpl, err := c.Compile("alert.html", "templates/", "alerts.AlertItem", alertItem, varList)
	if err != nil {
		return err
	}

	c.SetBaseImportMap(map[string]string{
		"io":                               "io",
		"github.com/Azareal/Gosora/common": "c github.com/Azareal/Gosora/common",
	})
	// TODO: Fix the import loop so we don't have to use this hack anymore
	c.SetBuildTags("!no_templategen,tmplgentopic")

	t := TItemHold(make(map[string]TItem))

	topicsRow := &TopicsRow{1, "topic-title", "Topic Title", "The topic content.", 1, false, false, now, now, user3.ID, 1, 1, "", "::1", 1, 0, 1, 0, 1, "classname", 0, "", user2, "", 0, user3, "General", "/forum/general.2", nil}
	t.AddStd("topics_topic", "c.TopicsRow", topicsRow)

	poll := Poll{ID: 1, Type: 0, Options: map[int]string{0: "Nothing", 1: "Something"}, Results: map[int]int{0: 5, 1: 2}, QuickOptions: []PollOption{
		PollOption{0, "Nothing"},
		PollOption{1, "Something"},
	}, VoteCount: 7}
	avatar, microAvatar := BuildAvatar(62, "")
	miniAttach := []*MiniAttachment{&MiniAttachment{Path: "/"}}
	topic := TopicUser{1, "blah", "Blah", "Hey there!", 62, false, false, now, now, 1, 1, 0, "", "::1", 1, 0, 1, 0, "classname", poll.ID, "weird-data", BuildProfileURL("fake-user", 62), "Fake User", Config.DefaultGroup, avatar, microAvatar, 0, "", "", "", 58, false, miniAttach, nil, false}
	var replyList []*ReplyUser
	// TODO: Do we really want the UID here to be zero?
	avatar, microAvatar = BuildAvatar(0, "")
	reply := Reply{1, 1, "Yo!", 1, Config.DefaultGroup, now, 0, 0, 1, "::1", true, 1, 1, ""}
	ru := &ReplyUser{ClassName: "", Reply: reply, CreatedByName: "Alice", Avatar: avatar, Level: 0, Attachments: miniAttach}
	ru.Init()
	replyList = append(replyList, ru)

	varList = make(map[string]tmpl.VarItem)
	header.Title = "Topic Name"
	tpage := TopicPage{header, replyList, topic, &Forum{ID: 1, Name: "Hahaha"}, poll, Paginator{[]int{1}, 1, 1}}
	tpage.Forum.Link = BuildForumURL(NameToSlug(tpage.Forum.Name), tpage.Forum.ID)
	t.AddStd("topic_posts", "c.TopicPage", tpage)
	t.AddStd("topic_alt_posts", "c.TopicPage", tpage)

	itemsPerPage := 25
	_, page, lastPage := PageOffset(20, 1, itemsPerPage)
	pageList := Paginate(page, lastPage, 5)
	t.AddStd("paginator", "c.Paginator", Paginator{pageList, page, lastPage})

	t.AddStd("topic_c_edit_post", "c.TopicCEditPost", TopicCEditPost{ID: 0, Source: "", Ref: ""})
	t.AddStd("topic_c_attach_item", "c.TopicCAttachItem", TopicCAttachItem{ID: 1, ImgSrc: "", Path: "", FullPath: ""})
	t.AddStd("topic_c_poll_input", "c.TopicCPollInput", TopicCPollInput{Index: 0})

	parti := []*User{user}
	convo := &Conversation{1, BuildConvoURL(1), user.ID, time.Now(), 0, time.Now()}
	convoItems := []ConvoViewRow{ConvoViewRow{&ConversationPost{1, 1, "hey", "", user.ID}, user, "", 4, true}}
	convoPage := ConvoViewPage{header, convo, convoItems, parti, true, Paginator{[]int{1}, 1, 1}}
	t.AddStd("convo", "c.ConvoViewPage", convoPage)

	t.AddStd("notice", "string", "nonono")

	dirPrefix := "./tmpl_client/"
	writeTemplate := func(name, content string) {
		log.Print("Writing template '" + name + "'")
		if content == "" {
			return //log.Fatal("No content body")
		}
		wg.Add(1)
		go func() {
			tname := themeName
			if tname != "" {
				tname = "_" + tname
			}
			err := writeFile(dirPrefix+"template_"+name+tname+".jgo", content)
			if err != nil {
				log.Fatal(err)
			}
			wg.Done()
		}()
	}

	log.Print("Writing the templates")
	for name, titem := range t {
		log.Print("Writing " + name)
		varList := make(map[string]tmpl.VarItem)
		tmpl, err := c.Compile(name+".html", "templates/", titem.Expects, titem.ExpectsInt, varList)
		if err != nil {
			return err
		}
		writeTemplate(name, tmpl)
	}
	writeTemplate("alert", alertTmpl)
	/*//writeTemplate("forum", forumTmpl)
	writeTemplate("topic_posts", topicPostsTmpl)
	writeTemplate("topic_alt_posts", topicAltPostsTmpl)
	writeTemplateList(c, &wg, dirPrefix)*/
	return nil
}

func getTemplateList(c *tmpl.CTemplateSet, wg *sync.WaitGroup, prefix string) string {
	DebugLog("in getTemplateList")
	pout := "\n// nolint\nfunc init() {\n"
	tFragCount := make(map[string]int)
	bodyMap := make(map[string]string) //map[body]fragmentPrefix
	//tmplMap := make(map[string]map[string]string) // map[tmpl]map[body]fragmentPrefix
	tmpCount := 0
	for _, frag := range c.FragOut {
		front := frag.TmplName + "_frags[" + strconv.Itoa(frag.Index) + "]"
		DebugLog("front: ", front)
		DebugLog("frag.Body: ", frag.Body)
		/*bodyMap, tok := tmplMap[frag.TmplName]
		if !tok {
			tmplMap[frag.TmplName] = make(map[string]string)
			bodyMap = tmplMap[frag.TmplName]
		}*/
		fp, ok := bodyMap[frag.Body]
		if !ok {
			bodyMap[frag.Body] = front
			var bits string
			DebugLog("encoding f.Body")
			for _, char := range []byte(frag.Body) {
				if char == '\'' {
					bits += "'\\" + string(char) + "',"
				} else if char < 32 {
					bits += strconv.Itoa(int(char)) + ","
				} else {
					bits += "'" + string(char) + "',"
				}
			}
			tmpStr := strconv.Itoa(tmpCount)
			pout += "arr_" + tmpStr + " := [...]byte{" + bits + "}\n"
			pout += front + " = arr_" + tmpStr + "[:]\n"
			tmpCount++
			//pout += front + " = []byte(`" + frag.Body + "`)\n"
		} else {
			DebugLog("encoding cached index " + fp)
			pout += front + " = " + fp + "\n"
		}

		_, ok = tFragCount[frag.TmplName]
		if !ok {
			tFragCount[frag.TmplName] = 0
		}
		tFragCount[frag.TmplName]++
	}

	out := "package " + c.GetConfig().PackageName + "\n\n"
	getterstr := "\n// nolint\nGetFrag = func(name string) [][]byte {\nswitch(name) {\n"
	for templateName, count := range tFragCount {
		//out += "var " + templateName + "_frags = make([][]byte," + strconv.Itoa(count) + ")\n"
		out += "var " + templateName + "_frags [" + strconv.Itoa(count) + "][]byte\n"
		getterstr += "\tcase \"" + templateName + "\":\n"
		//getterstr += "\treturn " + templateName + "_frags\n"
		getterstr += "\treturn " + templateName + "_frags[:]\n"
	}
	getterstr += "}\nreturn nil\n}\n"
	out += pout + "\n" + getterstr + "}\n"

	return out
}

func writeTemplateList(c *tmpl.CTemplateSet, wg *sync.WaitGroup, prefix string) {
	log.Print("Writing template list")
	wg.Add(1)
	go func() {
		err := writeFile(prefix+"template_list.go", getTemplateList(c, wg, prefix))
		if err != nil {
			log.Fatal(err)
		}
		wg.Done()
	}()
	wg.Wait()
}

func arithToInt64(in interface{}) (out int64) {
	switch in := in.(type) {
	case int64:
		out = in
	case int32:
		out = int64(in)
	case int:
		out = int64(in)
	case uint32:
		out = int64(in)
	case uint16:
		out = int64(in)
	case uint8:
		out = int64(in)
	case uint:
		out = int64(in)
	}
	return out
}

func arithDuoToInt64(left, right interface{}) (leftInt, rightInt int64) {
	return arithToInt64(left), arithToInt64(right)
}

func initDefaultTmplFuncMap() {
	// TODO: Add support for floats
	fmap := make(map[string]interface{})
	fmap["add"] = func(left, right interface{}) interface{} {
		leftInt, rightInt := arithDuoToInt64(left, right)
		return leftInt + rightInt
	}

	fmap["subtract"] = func(left, right interface{}) interface{} {
		leftInt, rightInt := arithDuoToInt64(left, right)
		return leftInt - rightInt
	}

	fmap["multiply"] = func(left, right interface{}) interface{} {
		leftInt, rightInt := arithDuoToInt64(left, right)
		return leftInt * rightInt
	}

	fmap["divide"] = func(left, right interface{}) interface{} {
		leftInt, rightInt := arithDuoToInt64(left, right)
		if leftInt == 0 || rightInt == 0 {
			return 0
		}
		return leftInt / rightInt
	}

	fmap["dock"] = func(dock, headerInt interface{}) interface{} {
		return template.HTML(BuildWidget(dock.(string), headerInt.(*Header)))
	}

	fmap["hasWidgets"] = func(dock, headerInt interface{}) interface{} {
		return HasWidgets(dock.(string), headerInt.(*Header))
	}

	fmap["elapsed"] = func(startedAtInt interface{}) interface{} {
		return time.Since(startedAtInt.(time.Time)).String()
	}

	fmap["lang"] = func(phraseNameInt interface{}) interface{} {
		phraseName, ok := phraseNameInt.(string)
		if !ok {
			panic("phraseNameInt is not a string")
		}
		// TODO: Log non-existent phrases?
		return template.HTML(p.GetTmplPhrase(phraseName))
	}

	// TODO: Implement this in the template generator too
	fmap["langf"] = func(phraseNameInt interface{}, args ...interface{}) interface{} {
		phraseName, ok := phraseNameInt.(string)
		if !ok {
			panic("phraseNameInt is not a string")
		}
		// TODO: Log non-existent phrases?
		// TODO: Optimise TmplPhrasef so we don't use slow Sprintf there
		return template.HTML(p.GetTmplPhrasef(phraseName, args...))
	}

	fmap["level"] = func(levelInt interface{}) interface{} {
		level, ok := levelInt.(int)
		if !ok {
			panic("levelInt is not an integer")
		}
		return template.HTML(p.GetLevelPhrase(level))
	}

	fmap["bunit"] = func(byteInt interface{}) interface{} {
		var byteFloat float64
		var unit string
		switch bytes := byteInt.(type) {
		case int:
			byteFloat, unit = ConvertByteUnit(float64(bytes))
		case int64:
			byteFloat, unit = ConvertByteUnit(float64(bytes))
		case uint64:
			byteFloat, unit = ConvertByteUnit(float64(bytes))
		case float64:
			byteFloat, unit = ConvertByteUnit(bytes)
		default:
			panic("bytes is not an int, int64 or uint64")
		}
		return fmt.Sprintf("%.1f", byteFloat) + unit
	}

	fmap["abstime"] = func(timeInt interface{}) interface{} {
		time, ok := timeInt.(time.Time)
		if !ok {
			panic("timeInt is not a time.Time")
		}
		return time.Format("2006-01-02 15:04:05")
	}

	fmap["reltime"] = func(timeInt interface{}) interface{} {
		time, ok := timeInt.(time.Time)
		if !ok {
			panic("timeInt is not a time.Time")
		}
		return RelativeTime(time)
	}

	fmap["scope"] = func(name interface{}) interface{} {
		return ""
	}

	fmap["dyntmpl"] = func(nameInt, pageInt, headerInt interface{}) interface{} {
		header := headerInt.(*Header)
		err := header.Theme.RunTmpl(nameInt.(string), pageInt, header.Writer)
		if err != nil {
			return err
		}
		return ""
	}

	fmap["ptmpl"] = func(nameInt, pageInt, headerInt interface{}) interface{} {
		header := headerInt.(*Header)
		err := header.Theme.RunTmpl(nameInt.(string), pageInt, header.Writer)
		if err != nil {
			return err
		}
		return ""
	}

	fmap["js"] = func() interface{} {
		return false
	}

	fmap["flush"] = func() interface{} {
		return nil
	}

	DefaultTemplateFuncMap = fmap
}

func loadTemplates(t *template.Template, themeName string) error {
	t.Funcs(DefaultTemplateFuncMap)
	tFiles, err := filepath.Glob("templates/*.html")
	if err != nil {
		return err
	}

	tFileMap := make(map[string]int)
	for index, path := range tFiles {
		path = strings.Replace(path, "\\", "/", -1)
		log.Print("templateFile: ", path)
		if skipCTmpl(path) {
			log.Print("skipping")
			continue
		}
		tFileMap[path] = index
	}

	overrideFiles, err := filepath.Glob("templates/overrides/*.html")
	if err != nil {
		return err
	}
	for _, path := range overrideFiles {
		path = strings.Replace(path, "\\", "/", -1)
		log.Print("overrideFile: ", path)
		if skipCTmpl(path) {
			log.Print("skipping")
			continue
		}
		index, ok := tFileMap["templates/"+strings.TrimPrefix(path, "templates/overrides/")]
		if !ok {
			log.Print("not ok: templates/" + strings.TrimPrefix(path, "templates/overrides/"))
			tFiles = append(tFiles, path)
			continue
		}
		tFiles[index] = path
	}

	if themeName != "" {
		overrideFiles, err := filepath.Glob("./themes/" + themeName + "/overrides/*.html")
		if err != nil {
			return err
		}
		for _, path := range overrideFiles {
			path = strings.Replace(path, "\\", "/", -1)
			log.Print("overrideFile: ", path)
			if skipCTmpl(path) {
				log.Print("skipping")
				continue
			}
			index, ok := tFileMap["templates/"+strings.TrimPrefix(path, "themes/"+themeName+"/overrides/")]
			if !ok {
				log.Print("not ok: templates/" + strings.TrimPrefix(path, "themes/"+themeName+"/overrides/"))
				tFiles = append(tFiles, path)
				continue
			}
			tFiles[index] = path
		}
	}

	template.Must(t.ParseFiles(tFiles...))
	template.Must(t.ParseGlob("pages/*"))
	return nil
}

func InitTemplates() error {
	DebugLog("Initialising the template system")
	initDefaultTmplFuncMap()

	// The interpreted templates...
	DebugLog("Loading the template files...")
	return loadTemplates(DefaultTemplates, "")
}