package goPress

import (
	"bytes"
	"encoding/json"
	"github.com/eknkc/amber"
	"github.com/hoisie/web"
	"github.com/murz/go-handlebars/handlebars"
	"github.com/russross/blackfriday"
	"io/ioutil"
	"log"
	"os"
	"strings"
	"time"
)

//
// Function:          Mainpage
//
// Description:       This function is used to generate and display the main page for the
//                    web site. This function will guide the user to setup the DropBox
//                    account if this is the first time being ran or the dropbox authorization
//                    secret gets zeroed.
//
// Inputs:
//
func Mainpage(ctx *web.Context) string {
	//
	// Render the main page.
	//
	page := RenderPageContents(ctx, SiteData.mainpg, SiteData.Sitebase+"pages/main")

	return page
}

//
// Function:          SiteMap
//
// Description:       This function is to give a site map to requesters.
//
// Inputs:
//
func SiteMap(ctx *web.Context) string {
	var contents string

	wfile, err := os.Open(SiteData.Sitebase + "sitemap.xml")
	if err == nil {
		bcontents, err := ioutil.ReadAll(wfile)
		err = err
		contents = string(bcontents)
		wfile.Close()
	}
	return contents
}

//
// Function:          PostPages
//
// Description:       This function generates the needed post page.
//
// Inputs:
//                    ctx        What the broser sends
//                    posttype   The type of post
//                    postname  The name of the post type instance
//                    val            The name of the post page to display
//
func PostPages(ctx *web.Context, posttype string, postname string, val string) string {
	//
	// Get the page contents and process it.
	//
	pgloc := SiteData.postbase + posttype + "/" + postname + "/" + val
	return RenderPageContents(ctx, GetPageContents(pgloc), pgloc)
}

//
// Function:          PostIndex
//
// Description:       This function generates the needed post index.
//
// Inputs:
//                    ctx            What the broser sends
//                    posttype   The type of post
//                    postname  The name of the post type instance
//
func PostIndex(ctx *web.Context, posttype string, postname string) string {
	//
	// Get the page contents and process it.
	//
	pgloc := SiteData.postbase + posttype + "/" + postname + "/index"
	return RenderPageContents(ctx, GetPageContents(pgloc), pgloc)
}

//
// Function:          topPages
//
// Description:       This function will generate a "static" top level page that is not
//                    a post page.
//
// Inputs:
//                    val         The name of the top level page
//
func TopPages(ctx *web.Context, val string) string {
	//
	// Look for the markdown of the page.
	//
	pgloc := SiteData.Sitebase + "pages/" + val
	return RenderPageContents(ctx, GetPageContents(pgloc), pgloc)
}

//
// Function:          GetPageContents
//
// Description:       This function is used to retrive the page contents. It will first look for
//                    a markdown page, and then an html page. It will also process the page for
//                    shortcodes.
//
// Inputs:
//
func GetPageContents(filename string) string {
	//
	// Assume the page can not be found.
	//
	contents := SiteData.content["404"]

	//
	// Let's look for a markdown version first.
	//
	wfile, err := os.Open(filename + ".md")
	if err == nil {
		bcontents, _ := ioutil.ReadAll(wfile)
		wfile.Close()
		contents = string(blackfriday.MarkdownCommon(bcontents))
		//
		// Double quotes were turned into &ldquo; and
		// &rdquo;. Turn them back. Without this, and
		// Handlebar macros will be broken.
		//
		contents = strings.Replace(contents, "&ldquo;", "\"", -1)
		contents = strings.Replace(contents, "&rdquo;", "\"", -1)
	} else {
		//
		// It must be an html. Look for that.
		//
		wfile, err = os.Open(filename + ".html")
		if err == nil {
			bcontents, _ := ioutil.ReadAll(wfile)
			contents = string(bcontents)
			wfile.Close()
		} else {
			//
			// It must be an amber. Look for that.
			//
			wfile, err = os.Open(filename + ".amber")
			if err == nil {
				wfile.Close()
				template, err2 := amber.CompileFile(filename+".amber", amber.Options{true, false})
				if err2 != nil {
					//
					// Bad amber file.

					log.Println("Amber file bad: " + filename)
				} else {
					//
					// Put the default site info.
					//
					pgData := SiteData.content

					//
					// read in that pages specific data to be added to the rest
					// of the data. It is stored at the same place, but in a json
					// file.
					//
					if wfile, err := os.Open(filename + ".json"); err == nil {
						//
						// Load the json file of extra data for this page. This could override
						// the standard data as well.
						//
						enc := json.NewDecoder(wfile)
						enc.Decode(&pgData)
						wfile.Close()
					} else {
						log.Println("The page: " + filename + " did not have a json file.")
					}

					pgData["PageName"] = filename

					//
					// The amber source compiles okay. Run the template and return
					// the results.
					//
					var b bytes.Buffer
					template.Execute(&b, pgData)
					contents = b.String()
				}
			} else {
				//
				// A file could not be found.
				//
				log.Println("Could not find file:  " + filename)
			}
		}
	}

	//
	// Return the file contains obtained.
	//
	return contents
}

//
// Function:          RenderPageContents
//
// Description:       This function is used to process and render the contents of a page.
//                    It can be the main page, or a post page, or any page. We accept the
//                    input as the contents for the page template, run the page template
//                    with it, process all shortcodes and embedded codes, and return the
//                    results.
//
// Inputs:
//                    contents          The pages main contents.
//
func RenderPageContents(ctx *web.Context, contents string, filename string) string {
	//
	// Set the header information
	SetStandardHeader(ctx)

	//
	// Put the default site info.
	//
	pgData := SiteData.content

	//
	// Add data specific to this page.
	//
	pgData["content"] = contents

	//
	// read in that pages specific data to be added to the rest
	// of the data. It is stored at the same place, but in a json
	// file.
	//
	if wfile, err := os.Open(filename + ".json"); err == nil {
		//
		// Load the json file of extra data for this page. This could override
		// the standard data as well.
		//
		enc := json.NewDecoder(wfile)
		enc.Decode(&pgData)
		wfile.Close()
	} else {
		log.Println("The page: " + filename + " did not have a json file.")
	}

	pgData["PageName"] = filename

	//
	// Register the helpers.
	//
	// NOTICE: All helpers can not have spaces in the parameter. Therefore,
	//                  all of these helpers assume a "-" is a space. It gets translated to
	//                  a space before using.
	//
	// Helper: 		save
	//
	// Description:     This helper allows you do define macros for expanding
	//                            inside the template. You give it a name, "|", and text to
	//                            expand into. Currently, all spaces have to be "-".
	//
	handlebars.RegisterHelper("save", func(params ...interface{}) string {
		if text, ok := params[0].(string); ok {
			parts := strings.Split(text, "|")
			content := strings.Replace(parts[1], "-", " ", -1)
			pgData[parts[0]] = content
			return content
		}
		return ""
	})

	//
	//  The format has to use these sets of constants:
	//            Stdlongmonth = "January"
	//            Stdmonth = "Jan"
	//            Stdnummonth = "1"
	//            Stdzeromonth = "01"
	//            Stdlongweekday = "Monday"
	//            Stdweekday = "Mon"
	//            Stdday = "2"
	//            Stdunderday = "_2"
	//            Stdzeroday = "02"
	//            Stdhour = "15"
	//            stdHour12 = "3"
	//            stdZeroHour12 = "03"
	//            Stdminute = "4"
	//            Stdzerominute = "04"
	//            Stdsecond = "5"
	//            Stdzerosecond = "05"
	//            Stdlongyear = "2006"
	//            Stdyear = "06"
	//            Stdpm = "Pm"
	//            Stdpm = "Pm"
	//            Stdtz = "Mst"
	//
	//  Helper:			date
	//
	//  Description:              This helper prints the current date/time in the format
	//                     		 given. Please refer to the above chart for proper
	//                                      format codes. EX:  07/20/2015 is "01/02/2006"
	//
	handlebars.RegisterHelper("date", func(params ...interface{}) string {
		if format, ok := params[0].(string); ok {
			format = strings.Replace(format, "-", " ", -1)
			tnow := time.Now()
			return tnow.Format(format)
		}
		return ""
	})

	//
	// Render the current for the first pass.
	//
	page := handlebars.Render(SiteData.template, pgData)

	//
	// Process any shortcodes on the page.
	//
	page1 := ProcessShortCodes(page)

	//
	// Reder new content from Short Code and filters.
	//
	page2 := handlebars.Render(page1, pgData)

	//
	// Return the results.
	//
	return page2
}

//
// Function:          SetStandardHeader
//
// Description:       This function is used as a one place for setting the standard
//                    header information.
//
// Inputs:
//
func SetStandardHeader(ctx *web.Context) {
	//
	// Set caching for the item
	//
	ctx.SetHeader("Cache-Control", "public", false)

	//
	// Set the maxamum age to one month (30 days)
	//
	ctx.SetHeader("Cache-Control", "max-age=2592000", false)

	//
	// Set the name to gpPress for the server type.
	//
	ctx.SetHeader("Server", "goPress - a CMS written in go from Custom Computer Tools: http://customct.com.", true)
}
