package main

import (
	"flag"
	"fmt"
	"image/jpeg"
	"image/png"
	"net/http"
	"net/url"
	"os"
	"sort"
	"strings"

	"github.com/pkg/errors"

	"github.com/gorilla/mux"
	shellquote "github.com/kballard/go-shellquote"
	"github.com/mattermost/mattermost-server/v5/model"
	"github.com/mattermost/mattermost-server/v5/plugin"

	"github.com/mattermost/mattermost-plugin-memes/server/meme"
	"github.com/mattermost/mattermost-plugin-memes/server/memelibrary"
)

const memeCommand = "x"

func resolveMeme(input string) (*meme.Template, []string, error) {
	if template, text := memelibrary.PatternMatch(input); template != nil {
		return template, text, nil
	}
	parts, err := shellquote.Split(input)
	if err != nil {
		return nil, nil, err
	}
	if template := memelibrary.Template(parts[0]); template != nil {
		return template, parts[1:], nil
	}
	return nil, nil, fmt.Errorf("i don't know this meme")
}

func demo(args []string) error {
	fs := flag.NewFlagSet("demo", flag.ContinueOnError)
	out := fs.String("out", "", "output path to write the meme to")
	if err := fs.Parse(args); err != nil {
		return err
	}

	input := fs.Args()
	if len(input) != 1 {
		return fmt.Errorf("specify an input string")
	}

	template, text, err := resolveMeme(input[0])
	if err != nil {
		return err
	}

	if *out != "" {
		f, err := os.Create(*out)
		if err != nil {
			return err
		}
		defer f.Close()

		img, err := template.Render(text)
		if err != nil {
			return err
		}
		if err := jpeg.Encode(f, img, &jpeg.Options{
			Quality: 100,
		}); err != nil {
			return err
		}
	}

	return nil
}

func serveTemplateJPEG(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	templateName := vars["name"]

	template := memelibrary.Template(templateName)
	if template == nil {
		http.NotFound(w, r)
		return
	}

	query := r.URL.Query()
	text := query["text"]

	img, err := template.Render(text)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "image/jpeg")
	w.Header().Set("Cache-Control", "public, max-age=604800")
	if err := jpeg.Encode(w, img, &jpeg.Options{
		Quality: 90,
	}); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

func serveTemplatePNG(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	templateName := vars["name"]

	template := memelibrary.Template(templateName)
	if template == nil {
		http.NotFound(w, r)
		return
	}

	query := r.URL.Query()
	text := query["text"]

	img, err := template.Render(text)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "image/png")
	w.Header().Set("Cache-Control", "public, max-age=604800")
	if err := png.Encode(w, img); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}


type Plugin struct {
	plugin.MattermostPlugin

	router *mux.Router
}

func (p *Plugin) OnActivate() error {
	if p.API.GetConfig().ServiceSettings.SiteURL == nil {
		p.API.LogError("SiteURL must be set. Some features will operate incorrectly if the SiteURL is not set. See documentation for details: http://about.mattermost.com/default-site-url")
	}
	p.router = mux.NewRouter()
	p.router.HandleFunc("/templates/{name}.jpg", serveTemplateJPEG).Methods("GET")
	p.router.HandleFunc("/templates/{name}.png", serveTemplatePNG).Methods("GET")
	if err := p.API.RegisterCommand(createMemesCommand()); err != nil {
		return errors.Wrapf(err, "failed to register %s command", memeCommand)
	}
	return nil
}

func createMemesCommand() *model.Command {
	var availableMemes = getAvailableMemes()
	memes := model.NewAutocompleteData(memeCommand, "[pic-name]", "*meows affectionately*")

	for _, name := range availableMemes {
		currentMeme := model.NewAutocompleteData(name, fmt.Sprintf("/plugins/memes/templates/%s.png", name), "meow")
		currentMeme.AddTextArgument("displays text on pic", "[text]", "")
		memes.AddCommand(currentMeme)
	}

	return &model.Command{
		Trigger:          memeCommand,
		AutoComplete:     true,
		AutoCompleteDesc: "posts cringe",
		AutocompleteData: memes,
	}
}

func getAvailableMemes() []string {
	var availableMemes []string
	for name, metadata := range memelibrary.Memes() {
		availableMemes = append(availableMemes, name)
		availableMemes = append(availableMemes, metadata.Aliases...)
	}
	sort.Strings(availableMemes)
	return availableMemes
}

func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) {
	p.router.ServeHTTP(w, r)
}

func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
	siteURL := p.GetSiteURL()

	input := strings.TrimSpace(strings.TrimPrefix(args.Command, "/x"))

	if input == "" || input == "help" {
		var availableMemes = getAvailableMemes()
		return &model.CommandResponse{
			ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL,
			Text: `You can get started meming in one of two ways:

If your meme has well-defined phrasing, you can just type it:

` + "`" + `/meme brace yourself. memes are coming.` + "`" + `

If your meme doesn't have well-defined phrasing or you want more control, you can name a meme, then follow it with text to fill the slots:

` + "`" + `/meme brace-yourself "brace yourself." "memes are coming."` + "`" + `

In either case...

![brace-yourself](` + siteURL + `/plugins/memes/templates/brace-yourselves.jpg?text=brace+yourself.&text=memes+are+coming.)

Available memes: ` + strings.Join(availableMemes, ", "),
		}, nil
	}

	template, text, err := resolveMeme(input)
	if err != nil {
		return nil, model.NewAppError("ExecuteCommand", "error resolving meme", nil, err.Error(), http.StatusInternalServerError)
	}

	queryString := ""
	for _, t := range text {
		if queryString == "" {
			queryString += "?"
		} else {
			queryString += "&"
		}
		queryString += "text=" + url.QueryEscape(t)
	}

	resp := &model.CommandResponse{
		ResponseType: model.COMMAND_RESPONSE_TYPE_IN_CHANNEL,
		Text:         "![" + template.Name + "](" + siteURL + "/plugins/memes/templates/" + template.Name + ".png" + queryString + ")",
	}
	return resp, nil
}

func (p *Plugin) GetSiteURL() string {
	siteURL := ""
	ptr := p.API.GetConfig().ServiceSettings.SiteURL
	if ptr != nil {
		siteURL = *ptr
	}
	return siteURL
}

func main() {
	if len(os.Args) > 1 {
		if err := demo(os.Args[1:]); err != nil {
			fmt.Println(err.Error())
			os.Exit(1)
		}
	} else {
		plugin.ClientMain(&Plugin{})
	}
}
