package handler

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"errors"
	"fmt"
	"github.com/golang/freetype/truetype"
	"github.com/gorilla/mux"
	"golang.org/x/image/draw"
	"golang.org/x/image/font"
	"golang.org/x/image/font/gofont/gobold"
	_ "golang.org/x/image/font/gofont/gobold"
	"golang.org/x/image/math/fixed"
	"html/template"
	"image"
	"image/jpeg"
	"image/png"
	"io"
	"os"
	"path/filepath"
	"status/model"
	"strconv"
	"strings"
	"time"

	//"image/png"
	"net/http"
)

func exist(path string) bool {
	_, err := os.Stat(path)
	return err == nil
}

func getPath(r rune, emojipath string) (string, error) {
	if r > 47 && r < 58 {
		return "", errors.New("numeric char")
	}
	name := fmt.Sprintf("%.4x", r)
	var path string

	path = fmt.Sprintf("%s/emoji_u%s.png", emojipath, name)

	if !exist(path) {
		return "", fmt.Errorf("%s does NOT exist", path)
	}

	return path, nil
}

func loadEmoji(r rune, size int, emojipath string) (image.Image, bool) {
	var img image.Image

	path, err := getPath(r, emojipath)
	if err != nil {
		//fmt.Fprintln(os.Stderr, err)
		return img, false
	}

	fp, err := os.Open(path)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		return img, false
	}
	defer fp.Close()

	img, _, err = image.Decode(fp)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		return img, false
	}

	rect := image.Rect(0, 0, size, size)
	dst := image.NewRGBA(rect)
	draw.ApproxBiLinear.Scale(dst, rect, img, img.Bounds(), draw.Over, nil)

	return dst, true
}

func renderLine(img image.Image, dr *font.Drawer, s, emojipath string) {
	size := dr.Face.Metrics().Ascent.Floor() + dr.Face.Metrics().Descent.Floor()
	for _, r := range s {
		emoji, ok := loadEmoji(r, size, emojipath)

		if ok {
			// Drawer.Dot is glyph baseline of next glyph
			// get left/top coordinates for draw.Draw().
			p := image.Pt(dr.Dot.X.Floor(), dr.Dot.Y.Floor()-dr.Face.Metrics().Ascent.Floor())
			rect := image.Rect(0, 0, size, size).Add(p)

			// draw emoji and ascend baseline
			draw.Draw(img.(draw.Image), rect, emoji, image.ZP, draw.Over)
			dr.Dot.X += fixed.I(size)
		} else {
			// fallback: use normal glyph
		}
	}
}

func renderText(img image.Image, face font.Face, text, emojipath string) error {
	dr := &font.Drawer{
		Dst:  img.(draw.Image),
		Src:  image.White,
		Face: face,
		Dot:  fixed.Point26_6{},
	}
	for _, s := range strings.Split(text, "\n") {
		dr.Dot.X = fixed.I(6)
		dr.Dot.Y = fixed.I(13)

		renderLine(img, dr, s, emojipath)
	}
	return nil
}

func outputJPEG(img image.Image, w io.Writer) error {
	buf := new(bytes.Buffer)
	err := jpeg.Encode(buf, img, &jpeg.Options{Quality: 100})
	if err != nil {
		return err
	}

	_, err = io.Copy(w, buf)
	if err != nil {
		return err
	}

	return nil
}

func (h *Handler) showManageView(w http.ResponseWriter, r *http.Request) {
	logged, err := h.sess.Get(r)
	if err != nil {
		unauthorized(w, r)
		return
	}
	var page int64 = 0
	if val, ok := r.URL.Query()["page"]; ok && len(val[0]) == 1 {
		page, _ = strconv.ParseInt(val[0], 10, 64)
	}
	statuses, showMore, err := h.storage.StatusByUsername(logged, 20, page)
	if err != nil {
		serverError(w, err)
		return
	}
	session, err := h.sess.Store.Get(r, "status")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	flash := ""
	if flashes := session.Flashes(); len(flashes) > 0 {
		flash = flashes[0].(string)
	}
	session.Save(r, w)
	h.renderLayout(w, "manage", map[string]interface{}{
		"statuses":  statuses,
		"showMore":  showMore,
		"page":      page,
		"flash":     flash,
		"next_page": page + 1,
		"prev_page": page - 1,
	}, logged)
}

func (h *Handler) showUserView(w http.ResponseWriter, r *http.Request) {
	logged, _ := h.sess.Get(r)
	var page int64 = 0
	if val, ok := r.URL.Query()["page"]; ok && len(val[0]) == 1 {
		page, _ = strconv.ParseInt(val[0], 10, 64)
	}
	username := mux.Vars(r)["user"]
	user, err := h.storage.UserByName(username)
	if err != nil {
		notFound(w)
		return
	}
	statuses, showMore, err := h.storage.StatusByUsername(mux.Vars(r)["user"], 20, page)
	if err != nil {
		serverError(w, err)
		return
	}
	face := ""
	if len(statuses) > 0 {
		face = statuses[0].Face
	}
	h.renderLayout(w, "user", map[string]interface{}{
		"user":      username,
		"statuses":  statuses,
		"face":      face,
		"homepage":  user.Homepage,
		"about":     template.HTML(user.About),
		"picture":   user.Picture,
		"email":     user.Email,
		"showMore":  showMore,
		"page":      page,
		"next_page": page + 1,
		"prev_page": page - 1,
	}, logged)
}

type statusjson struct {
	Author  string `json:"author"`
	Content string `json:"content"`
	Face    string `json:"face"`
	TimeAgo string `json:"timeAgo"`
        Number  int `json:"number"` }

func (h *Handler) showUserStatusJSONView(w http.ResponseWriter, r *http.Request) {
	user := mux.Vars(r)["user"]
	if !h.storage.UserExists(user) {
		notFound(w)
		return
	}
	statuses, _, err := h.storage.StatusByUsername(mux.Vars(r)["user"], 1, 0)
	if err != nil {
		serverError(w, err)
		return
	}
	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Header().Set("Content-Type", "application/json")
	var res statusjson
	if len(statuses) > 0 {
		res.Author = statuses[0].User
		res.Content = statuses[0].ContentDisplay()
		res.Face = statuses[0].Face
		res.TimeAgo = statuses[0].TimeAgo()
	        res.Number = statuses[0].Number } 
	json.NewEncoder(w).Encode(res)
}

func (h *Handler) showUserStatusView(w http.ResponseWriter, r *http.Request) {
	statuses, _, err := h.storage.StatusByUsername(mux.Vars(r)["user"], 1, 0)
	if err != nil {
		serverError(w, err)
		return
	}
	var status model.Status
	if len(statuses) > 0 {
		status = statuses[0]
	}
	h.view("status").Execute(w, map[string]interface{}{"status": status})
}

func (h *Handler) showUserStatusImageViewEmoji(w http.ResponseWriter, r *http.Request) {
	user := mux.Vars(r)["user"]
	if !h.storage.UserExists(user) {
		notFound(w)
		return
	}
	statuses, _, err := h.storage.StatusByUsername(mux.Vars(r)["user"], 1, 0)
	if err != nil {
		serverError(w, err)
		return
	}

	face := "🙂"
	if len(statuses) > 0 {
		face = statuses[0].Face
	}

	f, err := os.Open(filepath.Join(h.cfg.AssetsDir, "badge.png"))
	if err != nil {
		serverError(w, err)
		return
	}

	img, err := png.Decode(f)
	if err != nil {
		serverError(w, err)
		return
	}

	ft, err := truetype.Parse(gobold.TTF)
	if err != nil {
		serverError(w, err)
		return
	}

	opt := truetype.Options{
		Size:              14,
		DPI:               0,
		Hinting:           0,
		GlyphCacheEntries: 0,
		SubPixelsX:        0,
		SubPixelsY:        0,
	}

	err = renderText(img, truetype.NewFace(ft, &opt), fmt.Sprintf("%s", face), h.cfg.EmojiFolder)
	if err != nil {
		serverError(w, err)
		return
	}

	err = outputJPEG(img, w)
	if err != nil {
		serverError(w, err)
		return
	}
}

func truncate(s string, max int) string {
	if len(s) > max {
		return s[:max] + "..."
	}
	return s
}

func (h *Handler) showAtomView(w http.ResponseWriter, r *http.Request) {
	username := mux.Vars(r)["user"]
	user, err := h.storage.UserByName(username)
	if err != nil {
		notFound(w)
		return
	}
	feed := Feed{
		Title: user.Name,
		ID:    fmt.Sprintf("https://status.cafe/users/%s/", user.Name),
		Author: &Person{
			Name: user.Name,
			URI:  fmt.Sprintf("https://status.cafe/users/%s", user.Name),
		},
		Updated: Time(time.Now()),
		Link: []Link{
			{
				Rel:  "self",
				Href: fmt.Sprintf("https://status.cafe/users/%s.atom", user.Name),
			},
			{
				Rel:  "alternate",
				Href: fmt.Sprintf("https://status.cafe/users/%s", user.Name),
				Type: "text/html",
			},
		},
		Icon: user.Picture,
		Logo: user.Picture,
	}
	statuses, _, err := h.storage.StatusByUsername(user.Name, 20, 0)
	if err != nil {
		serverError(w, err)
		return
	}
	for _, status := range statuses {
		feed.Entry = append(feed.Entry, createAtomEntryFromStatus(status))
	}
	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Header().Set("Content-Type", "application/atom+xml")
	var data []byte
	data, err = xml.MarshalIndent(&feed, "", "    ")
	if err != nil {
		serverError(w, err)
	}
	w.Write([]byte(xml.Header + string(data)))
}
