package plg_image_thumbnail

import (
	"bytes"
	_ "embed"
	"errors"
	"fmt"
	. "github.com/mickael-kerjean/filestash/server/common"
	"io"
	"net/http"
	"os"
	"os/exec"
	"sync"
	"time"
)

//go:embed dist/placeholder.png
var placeholder []byte

func init() {
	Hooks.Register.Thumbnailer("image/png", thumbnailBuilder{thumbnailPng})
	Hooks.Register.Thumbnailer("image/jpeg", thumbnailBuilder{thumbnailJpeg})
	for _, mType := range []string{
		"image/x-canon-cr2", "image/x-fuji-raf", "image/x-nikon-nef",
		"image/x-nikon-nrw", "image/x-epson-erf",
	} {
		Hooks.Register.Thumbnailer(mType, thumbnailBuilder{thumbnailRaw})
	}
	Hooks.Register.ProcessFileContentBeforeSend(renderRaw)
}

func thumbnailPng(reader io.ReadCloser, ctx *App, res *http.ResponseWriter, req *http.Request) (io.ReadCloser, error) {
	h := (*res).Header()
	r, err := createThumbnailForPng(reader)
	if err != nil {
		h.Set("Content-Type", "image/png")
		h.Set("Cache-Control", "max-age=1")
		return NewReadCloserFromBytes(placeholder), nil
	}
	h.Set("Content-Type", "image/webp")
	h.Set("Cache-Control", fmt.Sprintf("max-age=%d", 3600*12))
	return r, nil
}

func thumbnailJpeg(reader io.ReadCloser, ctx *App, res *http.ResponseWriter, req *http.Request) (io.ReadCloser, error) {
	h := (*res).Header()
	r, err := createThumbnailForJpeg(reader)
	if err != nil {
		h.Set("Content-Type", "image/png")
		h.Set("Cache-Control", "max-age=1")
		return NewReadCloserFromBytes(placeholder), nil
	}
	h.Set("Content-Type", "image/jpeg")
	h.Set("Cache-Control", fmt.Sprintf("max-age=%d", 3600*12))
	return r, nil
}

func thumbnailRaw(reader io.ReadCloser, ctx *App, res *http.ResponseWriter, req *http.Request) (io.ReadCloser, error) {
	h := (*res).Header()
	r, err := createThumbnailForRaw(reader)
	if err != nil {
		h.Set("Content-Type", "image/png")
		h.Set("Cache-Control", "max-age=1")
		return NewReadCloserFromBytes(placeholder), nil
	}
	h.Set("Content-Type", "image/jpeg")
	h.Set("Cache-Control", fmt.Sprintf("max-age=%d", 3600*12))
	return r, nil
}

func renderRaw(reader io.ReadCloser, ctx *App, res *http.ResponseWriter, req *http.Request) (io.ReadCloser, error) {
	query := req.URL.Query()
	if query.Get("thumbnail") == "true" {
		return reader, nil
	} else if isRaw(GetMimeType(query.Get("path"))) == false {
		return reader, nil
	} else if query.Get("size") == "" {
		return reader, nil
	}

	h := (*res).Header()
	r, err := createRenderingForRaw(reader, query.Get("size"))
	if err != nil {
		h.Set("Content-Type", "image/png")
		return NewReadCloserFromBytes(placeholder), nil
	}
	h.Set("Content-Type", "image/jpeg")
	h.Set("Cache-Control", fmt.Sprintf("max-age=%d", 3600*12))
	return r, nil
}

type thumbnailBuilder struct {
	fn func(reader io.ReadCloser, ctx *App, res *http.ResponseWriter, req *http.Request) (io.ReadCloser, error)
}

func (this thumbnailBuilder) Generate(reader io.ReadCloser, ctx *App, res *http.ResponseWriter, req *http.Request) (io.ReadCloser, error) {
	return this.fn(reader, ctx, res, req)
}

type ThumbnailExecutable struct {
	Name       string
	Binary     *[]byte
	Checksum   []byte
	isValid    bool
	lastVerify time.Time
	sync.Mutex
}

func (this ThumbnailExecutable) Init() {
	p := "/tmp/" + this.Name
	f, err := os.OpenFile(p, os.O_RDONLY, os.ModePerm)
	if err != nil {
		outFile, err := os.OpenFile(p, os.O_CREATE|os.O_WRONLY, os.ModePerm)
		if err != nil {
			Log.Warning("plg_image_thumbnail::init::run::openFile '%s'", this.Name)
			return
		}
		outFile.Write(*this.Binary)
		if err = outFile.Close(); err != nil {
			Log.Warning("plg_image_thumbnail::init::run::close '%s'", this.Name)
			return
		}
	}
	f.Close()
}

func (this *ThumbnailExecutable) verify() bool {
	this.Lock()
	defer this.Unlock()
	if time.Since(this.lastVerify) > 30*time.Second {
		this.lastVerify = time.Now()
		f, err := os.OpenFile("/tmp/"+this.Name, os.O_RDONLY, os.ModePerm)
		if err == nil && bytes.Equal([]byte(HashStream(f, 0)), this.Checksum) {
			this.isValid = true
		}
	}
	return this.isValid
}

func (this *ThumbnailExecutable) Execute(reader io.ReadCloser, params ...string) (io.ReadCloser, error) {
	if this.verify() == false {
		Log.Error("plg_image_thumbnail::execution abort after verification on '%s'", this.Name)
		reader.Close()
		return nil, ErrFilesystemError
	}
	var buf bytes.Buffer
	var errBuff bytes.Buffer
	cmd := exec.Command("/tmp/"+this.Name, params...)
	cmd.Stdin = reader
	cmd.Stdout = &buf
	cmd.Stderr = &errBuff
	if err := cmd.Run(); err != nil {
		reader.Close()
		Log.Debug("plg_image_thumbmail::resize %s ERR %s", this.Name, string(errBuff.Bytes()))
		return nil, errors.New(string(errBuff.Bytes()))
	}
	cmd.Wait()
	reader.Close()
	return NewReadCloserFromBytes(buf.Bytes()), nil
}
