package caddywebp

import (
	"bytes"
	"fmt"
	"github.com/caddyserver/caddy/v2"
	"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
	"github.com/caddyserver/caddy/v2/modules/caddyhttp"
	"github.com/chai2010/webp"
	"golang.org/x/image/bmp"
	"image"
	"image/gif"
	"image/jpeg"
	"image/png"
	"io"
	"log"
	"net/http"
	"os"
	"path"
	"strings"
)

const Quality = 80

func init() {
	log.Println("webp plugin")
	caddy.RegisterModule(Webp{})
	httpcaddyfile.RegisterHandlerDirective("webp", parseCaddyfile)
}

func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
	return Webp{}, nil
}

type Webp struct {
}

func (Webp) CaddyModule() caddy.ModuleInfo {
	return caddy.ModuleInfo{
		ID:  "http.handlers.webp",
		New: func() caddy.Module { return new(Webp) },
	}
}

func decodeGif(r io.Reader) (image.Image, error) {
	i, err := gif.DecodeAll(r)
	// Give up if we can't decode or decoded multiple images
	if err != nil || len(i.Image) != 1 {
		return nil, err
	}
	return i.Image[0], nil
}

func (s Webp) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
	if !strings.Contains(r.Header.Get("Accept"), "image/webp") {
		// Browser does not advertise webp support for this request so let's bail
		return next.ServeHTTP(w, r)
	}
	repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)

	root := repl.ReplaceAll("{http.vars.root}", ".")
	cacheFile:=fmt.Sprintf("%s%s",caddyhttp.SanitizedPathJoin(root + "/.webpcache/", r.URL.Path +"?"), r.URL.RawQuery)
	origFile := caddyhttp.SanitizedPathJoin(root, r.URL.Path)

	cstat, err:=os.Stat(cacheFile)
	if err==nil {
		// Cache file exists
		origStat, err:=os.Stat(origFile)
		if err == nil && origStat.ModTime().Before(cstat.ModTime()) {
			// Serve cached file and fly away
			fh,err:=os.Open(cacheFile)
			if err == nil {
				// We couldn't open for whatever reason so let's not serve it
				w.Header().Set("content-type", "image/webp")
				w.Header().Add("webpstatus", "fromcache")
				w.WriteHeader(http.StatusOK)
				io.Copy(w,fh)
				return nil
			}
		}

	}
	// Let's encode and create a cached file
	cachePath,_:=path.Split(cacheFile)
	_,err=os.Stat(cachePath)
	if err != nil {
		err=os.MkdirAll(cachePath, 0600)
		if err != nil {
			log.Printf("Creating cache path: " + err.Error())
		}
	}
	resp := &response{}

	err = next.ServeHTTP(resp, r)
	if err != nil {
		return err
	}

	ct := http.DetectContentType(resp.Body.Bytes())

	var decoder func(io.Reader) (image.Image, error)
	switch ct {
	case "image/jpeg":
		decoder = jpeg.Decode
	case "image/png":
		decoder = png.Decode
	case "image/bmp":
		decoder = bmp.Decode
	case "image/gif":
		decoder = decodeGif
	default:
		next.ServeHTTP(w, r)
	}
	img, err := decoder(bytes.NewReader(resp.Body.Bytes()))
	if err != nil || img == nil {
		log.Println(err)
		return next.ServeHTTP(w, r)
	}
	var buf bytes.Buffer
	err = webp.Encode(&buf, img, &webp.Options{Lossless: false, Quality: Quality})
	if err != nil {
		log.Printf(err.Error())
		return next.ServeHTTP(w, r)
	}


	fh,err:=os.Create(cacheFile)
	fh.Write(buf.Bytes())
	w.Write(buf.Bytes())

	buf.WriteTo(fh)
	fh.Close()
	w.Header().Set("content-Type", "image/webp")
	w.Header().Add("webpstatus", "tocache")

	w.WriteHeader(http.StatusOK)
	buf.WriteTo(w)
	return nil
}

type response struct {
	header http.Header
	Body   bytes.Buffer
}

func (s *response) Header() http.Header {
	return http.Header{}
}

func (s *response) Write(data []byte) (int, error) {
	s.Body.Write(data)
	return len(data), nil
}
func (s *response) WriteHeader(i int) {
	return
}
