package filter

import (
	"crypto/md5"
	"errors"
	"fmt"
	"image"
	"image/gif"
	"image/jpeg"
	"image/png"
	"net/url"
	"os"
	"path"
	"strconv"
	"strings"

	"gitbase.de/apairon/mark2web/pkg/helper"
	"gitbase.de/apairon/mark2web/pkg/jobm"
	"gitbase.de/apairon/mark2web/pkg/logger"
	"gitbase.de/apairon/mark2web/pkg/mark2web"
	"gitbase.de/apairon/mark2web/pkg/webrequest"
	"github.com/disintegration/imaging"
	"github.com/flosch/pongo2"
)

func parseImageParams(str string) (*mark2web.ImagingConfig, error) {
	p := mark2web.ImagingConfig{}
	if str == "" {
		helper.Merge(&p, mark2web.CurrentTreeNode.Config.Imaging)
		// Filename and Format are only valid for current image
		p.Filename = ""
		p.Format = ""
		return &p, nil
	}
	for _, s := range strings.Split(str, ",") {
		e := strings.Split(s, "=")
		if len(e) < 2 {
			return nil, fmt.Errorf("invalid image parameter: %s", s)
		}
		var err error
		switch e[0] {
		case "w":
			p.Width, err = strconv.Atoi(e[1])
		case "h":
			p.Height, err = strconv.Atoi(e[1])
		case "f":
			p.Filename = e[1]
		case "t":
			p.TargetDir = e[1]
		case "p":
			p.Process = e[1]
		case "a":
			p.Anchor = e[1]
		case "q":
			p.Quality, err = strconv.Atoi(e[1])
			if p.Quality < 0 || p.Quality > 100 {
				err = errors.New("q= must be between 1 and 100")
			}
		default:
			return nil, fmt.Errorf("invalid image parameter: %s", s)
		}
		if err != nil {
			return nil, fmt.Errorf("could not convert image parameter to correct value type for '%s': %s", s, err)
		}
	}
	return &p, nil
}

// ImageProcessFilter read the image url and process parameters and saves the resized/processed image
// param: w=WITDH,h=HEIGHT
func ImageProcessFilter(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) {
	imgSource := in.String()
	p, err := parseImageParams(param.String())
	if err != nil {
		return nil, &pongo2.Error{
			Sender:    "filter:image_resize",
			OrigError: err,
		}
	}
	if p == nil {
		return nil, &pongo2.Error{
			Sender:    "filter:image_resize",
			OrigError: errors.New("no imaging config defined"),
		}
	}

	var img image.Image
	if p.Process == "" {
		p.Process = "resize"
	}
	filePrefix := fmt.Sprintf(
		"%s_%dx%d_q%03d",
		p.Process,
		p.Width,
		p.Height,
		p.Quality,
	)
	if strings.HasPrefix(imgSource, "http://") || strings.HasPrefix(imgSource, "https://") {
		// build filename
		if p.Filename == "" {
			var fBase string
			if u, _ := url.Parse(imgSource); u != nil {
				fBase = path.Base(u.Path)
			}

			p.Filename = fmt.Sprintf(
				"%s_%x_%s",
				filePrefix,
				md5.Sum([]byte(imgSource)),
				fBase,
			)
		}
	} else {
		// local file
		imgSource = mark2web.CurrentTreeNode.ResolveInputPath(imgSource)
		if p.Filename == "" {
			p.Filename = fmt.Sprintf(
				"%s_%s",
				filePrefix,
				path.Base(imgSource),
			)
		}
	}

	var imgTarget string
	if p.TargetDir != "" {
		imgTarget = mark2web.CurrentTreeNode.ResolveOutputPath(
			path.Clean(p.TargetDir) + "/" +
				p.Filename,
		)

		pt := path.Dir(imgTarget)
		if _, err := os.Stat(pt); os.IsNotExist(err) {
			logger.I("create image target dir: %s", pt)
			if err := os.MkdirAll(pt, 0755); err != nil {
				return nil, &pongo2.Error{
					Sender:    "filter:image_resize",
					OrigError: fmt.Errorf("could not create image target dir '%s': %s", pt, err),
				}
			}
		}

		p.Filename = mark2web.CurrentTreeNode.ResolveNavPath(p.TargetDir + "/" + p.Filename)

	} else {
		imgTarget = mark2web.CurrentTreeNode.ResolveOutputPath(p.Filename)
	}

	if f, err := os.Stat(imgTarget); err == nil && !f.IsDir() {
		logger.N("skipped processing image from %s to %s, file already exists", imgSource, imgTarget)
	} else {
		jobm.Enqueue(jobm.Job{
			Function: func() {
				logger.N("processing image from %s to %s", imgSource, imgTarget)
				if strings.HasPrefix(imgSource, "http://") || strings.HasPrefix(imgSource, "https://") {
					// remote file
					img, p.Format, err = webrequest.GetImage(imgSource)
				} else {
					img, err = imaging.Open(imgSource, imaging.AutoOrientation(true))
				}
				logger.Eexit(err, "filter:image_resize, could not open image '%s'", imgSource)

				switch p.Process {
				case "resize":
					img = imaging.Resize(img, p.Width, p.Height, imaging.Lanczos)
				case "fit":
					img = imaging.Fit(img, p.Width, p.Height, imaging.Lanczos)
				case "fill":
					var anchor imaging.Anchor
					switch strings.ToLower(p.Anchor) {
					case "":
						fallthrough
					case "center":
						anchor = imaging.Center
					case "topleft":
						anchor = imaging.TopLeft
					case "top":
						anchor = imaging.Top
					case "topright":
						anchor = imaging.TopRight
					case "left":
						anchor = imaging.Left
					case "right":
						anchor = imaging.Right
					case "bottomleft":
						anchor = imaging.BottomLeft
					case "bottom":
						anchor = imaging.Bottom
					case "bottomright":
						anchor = imaging.BottomRight
					default:
						logger.Exit("filter:image_resize, unknown anchor a=%s definition", p.Anchor)
					}
					img = imaging.Fill(img, p.Width, p.Height, anchor, imaging.Lanczos)
				default:
					logger.Exit("filter:image_resize, invalid p parameter '%s'", p.Process)
				}

				if p.Format == "" {
					switch strings.ToLower(path.Ext(imgTarget)) {
					case ".jpg", ".jpeg", ".gif", ".png":
						var encodeOptions = make([]imaging.EncodeOption, 0)
						if p.Quality > 0 {
							encodeOptions = append(encodeOptions, imaging.JPEGQuality(p.Quality))
						}

						err = imaging.Save(img, imgTarget, encodeOptions...)
						logger.Eerr(err, "filter:image_resize, could save image '%s'", imgTarget)
					default:
						logger.Exit("filter:image_resize, invalid filename extension for image: %s", imgTarget)
					}
				} else {
					out, err := os.Create(imgTarget)
					defer out.Close()

					logger.Eexit(err, "filter:image_resize, could not create image file '%s'", imgTarget)
					switch p.Format {
					case "jpeg", "jpg":
						var jpegOpt *jpeg.Options
						if p.Quality > 0 {
							jpegOpt = &jpeg.Options{
								Quality: p.Quality,
							}
						}
						err = jpeg.Encode(out, img, jpegOpt)
					case "png":
						err = png.Encode(out, img)
					case "gif":
						err = gif.Encode(out, img, nil)
					default:
						logger.Exit("filter:image_resize, unknown format '%s' for '%s'", p.Format, imgSource)
					}
					logger.Eexit(err, "filter:image_resize, could not encode image file '%s'", imgTarget)
				}

				logger.N("finished image: %s", imgTarget)
			},
			Description: imgSource,
			Category:    "image process",
		})
	}
	return pongo2.AsValue(mark2web.CurrentTreeNode.ResolveNavPath(p.Filename)), nil
}
