package main

import (
	"bytes"
	"fmt"
	"regexp"
	"io/ioutil"
	"net/http"
	"path/filepath"

	"github.com/yuin/goldmark"
)

const (
	VALID_ID_REGEX = `^[0-9]+$`
)

func indexGet(w http.ResponseWriter, r *http.Request) {

	// get data from database
	cmd := "SELECT id,title FROM recipes ORDER BY title;"
	rows, err := db.Query(cmd)
	if err != nil {
		http.Error(w, "Failed to load recipes from database.", 500)
		return
	}
	defer rows.Close()

	// prepare data store
	type Element struct {
		Id string
		Title string
	}
	elements := make([]Element, 0)

	// scan database rows to data store
	for rows.Next() {
		var element Element
		err := rows.Scan(&element.Id, &element.Title)
		if err != nil {
			http.Error(w, "Could not parse recipe from database request.", 500)
			return
		} else {
			elements = append(elements, element)
		}
	}

	// render and return template
	path := filepath.Join(config.Http.Templates, "index.html")
	ServeTemplate(w, "index", path, elements)
}

func recipeGet(w http.ResponseWriter, r *http.Request) {

	// get id from URL parameters
	ids := r.URL.Query()["id"]
	if len(ids) != 1 {
		msg := fmt.Sprintf("Exactly 1 'id' URL parameter expected but %d provided.", len(ids))
		http.Error(w, msg, 400)
		return
	}
	idStr := ids[0]

	// validate id
	idRegex := regexp.MustCompile(VALID_ID_REGEX)
	if !(idRegex.MatchString(idStr)) {
		http.Error(w, "Bad 'id' URL parameter.", 400)
		return
	}

	// get data from database
	cmd := fmt.Sprintf("SELECT title,upstream_url,description_markdown FROM recipes WHERE (id='%s');", idStr)
	rows, err := db.Query(cmd)
	if err != nil {
		http.Error(w, "Database returned error: " + err.Error(), 500)
		return
	}
	defer rows.Close()

	// prepare data store
	type Element struct {
		Id string
		Title string
		UpstreamUrl string
		DescriptionMarkdown string
		RenderedDescriptionMarkdown string
	}
	elements := make([]Element, 0)

	// scan database rows to data store
	for rows.Next() {
		var element Element
		element.Id = idStr
		err := rows.Scan(&element.Title, &element.UpstreamUrl, &element.DescriptionMarkdown)
		if err != nil {
			http.Error(w, "Could not parse recipe from database request.", 500)
			return
		} else {
			elements = append(elements, element)
		}
	}

	// check result
	if len(elements) != 1 {
		http.Error(w, "Expected exactly 1 recipe from database.", 500)
		return
	}

	// render markdown
	var buf bytes.Buffer
	goldmark.Convert([]byte(elements[0].DescriptionMarkdown), &buf)
	elements[0].RenderedDescriptionMarkdown = buf.String()

	// render and return template
	path := filepath.Join(config.Http.Templates, "recipe.html")
	ServeTemplate(w, "recipe", path, elements[0])
}

func recipePost(w http.ResponseWriter, r *http.Request) {

	// get id from URL parameters
	ids := r.URL.Query()["id"]
	if len(ids) != 1 {
		msg := fmt.Sprintf("Exactly 1 'id' URL parameter expected but %d provided.", len(ids))
		http.Error(w, msg, 400)
		return
	}
	idStr := ids[0]

	// validate id
	idRegex := regexp.MustCompile(VALID_ID_REGEX)
	if !(idRegex.MatchString(idStr)) {
		http.Error(w, "Bad 'id' URL parameter.", 400)
		return
	}

	// read request body
	buffer,_ := ioutil.ReadAll(r.Body)  // FIXME error handling
	body := string(buffer)
	updateRecipe(body, idStr)
}

func recipeEditGet(w http.ResponseWriter, r *http.Request) {

	// get id from URL parameters
	ids := r.URL.Query()["id"]
	if len(ids) != 1 {
		http.Error(w, "Exactly 1 'id' URL parameter expected.", 400)
		return
	}
	idStr := ids[0]

	// validate id
	idRegex := regexp.MustCompile(VALID_ID_REGEX)
	if !(idRegex.MatchString(idStr)) {
		http.Error(w, "Bad 'id' URL parameter.", 400)
		return
	}

	// get data from database
	cmd := fmt.Sprintf("SELECT title,upstream_url,description_markdown FROM recipes WHERE (id='%s');", idStr)
	rows, err := db.Query(cmd)
	if err != nil {
		http.Error(w, "Got error from database: " + err.Error(), 500)
		return
	}
	defer rows.Close()

	// prepare data store
	type Element struct {
		Id string
		Title string
		UpstreamUrl string
		DescriptionMarkdown string
		RenderedDescriptionMarkdown string
	}
	elements := make([]Element, 0)

	// scan database rows to data store
	for rows.Next() {
		var element Element
		element.Id = idStr
		err := rows.Scan(&element.Title, &element.UpstreamUrl, &element.DescriptionMarkdown)
		if err != nil {
			http.Error(w, "Could not parse recipe from database request.", 500)
			return
		} else {
			elements = append(elements, element)
		}
	}

	// check result
	if len(elements) != 1 {
		http.Error(w, "Did not get exactly one recipe from database.", 500)
		return
	}

	// render and return template
	path := filepath.Join(config.Http.Templates, "recipe_edit.html")
	ServeTemplate(w, "recipe", path, elements[0])
}

func recipeEditPost(w http.ResponseWriter, r *http.Request) {

	// get id from URL parameters
	ids := r.URL.Query()["id"]
	if len(ids) != 1 {
		http.Error(w, "Exactly 1 'id' URL parameter expected.", 400)
		return
	}
	idStr := ids[0]

	// validate id
	idRegex := regexp.MustCompile(VALID_ID_REGEX)
	if !(idRegex.MatchString(idStr)) {
		http.Error(w, "Bad 'id' URL parameter.", 400)
		return
	}

	// read request body
	buffer,_ := ioutil.ReadAll(r.Body)  // FIXME error handling
	body := string(buffer)
	updateRecipe(body, idStr)
}

func updateRecipe(body string, idStr string) {

	// execute SQL UPDATE
	_,_ = db.Exec(`
		UPDATE
			recipes
		SET
			description_markdown=?
		WHERE
			(id=?);
		`,
		body, idStr,
	)  // FIXME error handling

	return
}

func recipeImageGet(w http.ResponseWriter, r *http.Request) {

	// get ID
	ids := r.URL.Query()["id"]
	if len(ids) != 1 {
		http.Error(w, "Expected exactly one 'id' URL parameter.", 400)
		return
	}
	idStr := ids[0]

	// validate ID
	idRegex := regexp.MustCompile(VALID_ID_REGEX)
	if !idRegex.MatchString(idStr) {
		http.Error(w, "Bad 'id' URL parameter.", 400)
		return
	}

	// serve image
	path := fmt.Sprintf("recipes/image/%s.jpg", idStr)
	ServeStorage(w, r, path)
}

func addRecipesGet(w http.ResponseWriter, r *http.Request) {

	filename := "add.html"
	path := filepath.Join(config.Http.Static, filename)
	http.ServeFile(w, r, path)
}

func addRecipesPost(w http.ResponseWriter, r *http.Request) {

	url := r.FormValue("url")
	title := r.FormValue("title")

	cmd := fmt.Sprintf("INSERT INTO recipes (title,upstream_url) VALUES ('%s', '%s')", title, url)
	res,err := db.Exec(cmd)
	if err != nil {
		http.Error(w, "Could not add recipe.", 500)
		return
	}
	id,err := res.LastInsertId()
	if err != nil {
		http.Error(w, "Expected exactly one recipe URL.", 400)
		return
	} else {
		redirect := fmt.Sprintf("/recipe?id=%d", id)
		http.Redirect(w, r, redirect, 303)
		return
	}
}

func staticGet(w http.ResponseWriter, r *http.Request, filename string) {

	path := filepath.Join(config.Http.Static, filename)
	http.ServeFile(w, r, path)
}