package wpdf

import (
	"bytes"
	"github.com/jung-kurt/gofpdf"
	"gogs.mikescher.com/BlackForestBytes/goext/dataext"
	"gogs.mikescher.com/BlackForestBytes/goext/imageext"
	"gogs.mikescher.com/BlackForestBytes/goext/langext"
	"image"
	"image/color"
	"image/draw"
	"net/http"
)

type PDFImageRef struct {
	Info  *gofpdf.ImageInfoType
	Name  string
	Bin   []byte
	Image *image.Image
	Mime  string
}

type PDFImageRegisterOpt struct {
	imageType             *string
	readDpi               *bool
	allowNegativePosition *bool
	name                  *string
}

func NewPDFImageRegisterOpt() *PDFImageRegisterOpt {
	return &PDFImageRegisterOpt{}
}

func (opt *PDFImageRegisterOpt) ImageType(v string) *PDFImageRegisterOpt {
	opt.imageType = &v
	return opt
}

func (opt *PDFImageRegisterOpt) ReadDpi(v bool) *PDFImageRegisterOpt {
	opt.readDpi = &v
	return opt
}

func (opt *PDFImageRegisterOpt) AllowNegativePosition(v bool) *PDFImageRegisterOpt {
	opt.allowNegativePosition = &v
	return opt
}

func (opt *PDFImageRegisterOpt) Name(v string) *PDFImageRegisterOpt {
	opt.name = &v
	return opt
}

func (b *WPDFBuilder) RegisterImage(bin []byte, opts ...*PDFImageRegisterOpt) *PDFImageRef {
	imgName := "fpdf_img_" + langext.MustRawHexUUID()
	imageType := ""
	readDpi := false
	allowNegativePosition := false
	mime := "application/octet-stream"

	for _, opt := range opts {
		imageType = langext.Coalesce(opt.imageType, imageType)
		readDpi = langext.Coalesce(opt.readDpi, readDpi)
		allowNegativePosition = langext.Coalesce(opt.allowNegativePosition, allowNegativePosition)
		imgName = langext.Coalesce(opt.name, imgName)
	}

	if imageType == "" {
		ct := ""
		if len(bin) > 512 {
			ct = http.DetectContentType(bin[:512])
		} else {
			ct = http.DetectContentType(bin)
		}
		switch ct {
		case "image/jpg":
			imageType = "JPG"
			mime = ct
		case "image/jpeg":
			imageType = "JPEG"
			mime = ct
		case "image/png":
			imageType = "PNG"
			mime = ct
		case "image/gif":
			imageType = "GIF"
			mime = ct
		}
	} else {
		switch imageType {
		case "JPG":
		case "JPEG":
			mime = "image/jpeg"
		case "PNG":
			mime = "image/png"
		case "GIF":
			mime = "image/gif"
		}
	}

	options := gofpdf.ImageOptions{
		ImageType:             imageType,
		ReadDpi:               readDpi,
		AllowNegativePosition: allowNegativePosition,
	}

	info := b.b.RegisterImageOptionsReader(imgName, options, bytes.NewReader(bin))

	return &PDFImageRef{
		Name:  imgName,
		Info:  info,
		Bin:   bin,
		Image: nil,
		Mime:  mime,
	}
}

type PDFImageOpt struct {
	x                     *float64
	y                     *float64
	width                 *float64
	height                *float64
	flow                  *bool
	link                  *int
	linkStr               *string
	imageType             *string
	readDpi               *bool
	allowNegativePosition *bool
	imageFit              *imageext.ImageFit
	fillColor             *color.Color
	compression           *imageext.ImageCompresson
	reEncodePixelPerMM    *float64
	crop                  *imageext.ImageCrop
	alphaOverride         *dataext.Tuple[float64, PDFBlendMode]
	debug                 *bool
}

func NewPDFImageOpt() *PDFImageOpt {
	return &PDFImageOpt{}
}

func (opt *PDFImageOpt) X(v float64) *PDFImageOpt {
	opt.x = &v
	return opt
}

func (opt *PDFImageOpt) Y(v float64) *PDFImageOpt {
	opt.y = &v
	return opt
}

func (opt *PDFImageOpt) Width(v float64) *PDFImageOpt {
	opt.width = &v
	return opt
}

func (opt *PDFImageOpt) Height(v float64) *PDFImageOpt {
	opt.height = &v
	return opt
}

func (opt *PDFImageOpt) Debug(v bool) *PDFImageOpt {
	opt.debug = &v
	return opt
}

func (opt *PDFImageOpt) Flow(v bool) *PDFImageOpt {
	opt.flow = &v
	return opt
}

func (opt *PDFImageOpt) Link(v int) *PDFImageOpt {
	opt.link = &v
	return opt
}

func (opt *PDFImageOpt) LinkStr(v string) *PDFImageOpt {
	opt.linkStr = &v
	return opt
}

func (opt *PDFImageOpt) ImageType(v string) *PDFImageOpt {
	opt.imageType = &v
	return opt
}

func (opt *PDFImageOpt) ReadDpi(v bool) *PDFImageOpt {
	opt.readDpi = &v
	return opt
}

func (opt *PDFImageOpt) AllowNegativePosition(v bool) *PDFImageOpt {
	opt.allowNegativePosition = &v
	return opt
}

func (opt *PDFImageOpt) ImageFit(v imageext.ImageFit) *PDFImageOpt {
	opt.imageFit = &v
	return opt

}

func (opt *PDFImageOpt) FillColor(v color.Color) *PDFImageOpt {
	opt.fillColor = &v
	return opt
}

func (opt *PDFImageOpt) Compression(v imageext.ImageCompresson) *PDFImageOpt {
	opt.compression = &v
	return opt
}

func (opt *PDFImageOpt) ReEncodePixelPerMM(v float64) *PDFImageOpt {
	opt.reEncodePixelPerMM = &v
	return opt
}

func (opt *PDFImageOpt) Crop(cropX float64, cropY float64, cropWidth float64, cropHeight float64) *PDFImageOpt {
	opt.crop = &imageext.ImageCrop{
		CropX:      cropX,
		CropY:      cropY,
		CropWidth:  cropWidth,
		CropHeight: cropHeight,
	}
	return opt
}

func (opt *PDFImageOpt) Alpha(alpha float64, blendMode PDFBlendMode) *PDFImageOpt {
	opt.alphaOverride = &dataext.Tuple[float64, PDFBlendMode]{V1: alpha, V2: blendMode}
	return opt
}

func (b *WPDFBuilder) Image(img *PDFImageRef, opts ...*PDFImageOpt) {
	var err error

	x := b.GetX()
	y := b.GetY()
	w := img.Info.Width()
	h := img.Info.Height()
	flow := true
	link := 0
	linkStr := ""
	imageType := ""
	readDpi := false
	allowNegativePosition := false
	reEncodePixelPerMM := 15.0
	var imageFit *imageext.ImageFit = nil
	var fillColor color.Color = color.Transparent
	compression := imageext.CompressionPNGSpeed
	debug := b.debug
	var crop *imageext.ImageCrop = nil
	var alphaOverride *dataext.Tuple[float64, PDFBlendMode]

	for _, opt := range opts {
		x = langext.Coalesce(opt.x, x)
		y = langext.Coalesce(opt.y, y)
		w = langext.Coalesce(opt.width, w)
		h = langext.Coalesce(opt.height, h)
		flow = langext.Coalesce(opt.flow, flow)
		link = langext.Coalesce(opt.link, link)
		linkStr = langext.Coalesce(opt.linkStr, linkStr)
		imageType = langext.Coalesce(opt.imageType, imageType)
		readDpi = langext.Coalesce(opt.readDpi, readDpi)
		allowNegativePosition = langext.Coalesce(opt.allowNegativePosition, allowNegativePosition)
		imageFit = langext.CoalesceOpt(opt.imageFit, imageFit)
		fillColor = langext.Coalesce(opt.fillColor, fillColor)
		compression = langext.Coalesce(opt.compression, compression)
		reEncodePixelPerMM = langext.Coalesce(opt.reEncodePixelPerMM, reEncodePixelPerMM)
		crop = langext.CoalesceOpt(opt.crop, crop)
		debug = langext.Coalesce(opt.debug, debug)
		alphaOverride = langext.CoalesceOpt(opt.alphaOverride, alphaOverride)
	}

	if flow {
		y = b.GetY()
	}

	regName := img.Name

	var subImageBounds *imageext.PercentageRectangle = nil

	if imageFit != nil || fillColor != nil || crop != nil {

		var dataimg image.Image
		if img.Image != nil {
			dataimg = *img.Image
		} else {
			dataimg, err = imageext.VerifyAndDecodeImage(bytes.NewReader(img.Bin), img.Mime)
			if err != nil {
				b.b.SetError(err)
				return
			}
		}

		if crop != nil {
			dataimg, err = imageext.CropImage(dataimg, crop.CropX, crop.CropY, crop.CropWidth, crop.CropHeight)
			if err != nil {
				b.b.SetError(err)
				return
			}
		}

		if imageFit != nil {
			pdfPixelPerMillimeter := 15.0

			pxw := w * pdfPixelPerMillimeter
			pxh := h * pdfPixelPerMillimeter

			var dataImgRect imageext.PercentageRectangle
			dataimg, dataImgRect, err = imageext.ObjectFitImage(dataimg, pxw, pxh, *imageFit, fillColor)
			if err != nil {
				b.b.SetError(err)
				return
			}

			subImageBounds = &dataImgRect
		}

		if dataimg.ColorModel() != color.RGBAModel && dataimg.ColorModel() != color.NRGBAModel {
			// the image cannto be 16bpp or similar - otherwise fpdf errors out
			dataImgRGBA := image.NewNRGBA(image.Rect(0, 0, dataimg.Bounds().Dx(), dataimg.Bounds().Dy()))
			draw.Draw(dataImgRGBA, dataImgRGBA.Bounds(), dataimg, dataimg.Bounds().Min, draw.Src)
			dataimg = dataImgRGBA
		}

		bfr, imgMime, err := imageext.EncodeImage(dataimg, compression)
		if err != nil {
			b.b.SetError(err)
			return
		}

		regName = regName + "_" + langext.MustRawHexUUID()

		switch imgMime {
		case "image/jpeg":
			imageType = "JPEG"
		case "image/png":
			imageType = "PNG"
		case "image/gif":
			imageType = "GIF"
		}

		b.b.RegisterImageOptionsReader(regName, gofpdf.ImageOptions{ImageType: imageType}, &bfr)

	}

	if alphaOverride != nil {
		oldA, oldBMS := b.b.GetAlpha()
		b.b.SetAlpha(alphaOverride.V1, string(alphaOverride.V2))
		defer func() { b.b.SetAlpha(oldA, oldBMS) }()
	}

	fpdfOpt := gofpdf.ImageOptions{
		ImageType:             imageType,
		ReadDpi:               readDpi,
		AllowNegativePosition: allowNegativePosition,
	}

	b.b.ImageOptions(regName, x, y, w, h, flow, fpdfOpt, link, linkStr)

	if debug {
		b.Rect(w, h, RectOutline, NewPDFRectOpt().X(x).Y(y).LineWidth(0.25).DrawColor(255, 0, 0))

		if subImageBounds != nil {
			r := subImageBounds.Of(imageext.Rectangle{X: x, Y: y, W: w, H: h})
			b.Rect(r.W, r.H, RectOutline, NewPDFRectOpt().X(r.X).Y(r.Y).LineWidth(0.25).DrawColor(255, 0, 0))
			b.Rect(r.W, r.H, RectFill, NewPDFRectOpt().X(r.X).Y(r.Y).FillColor(255, 0, 0).Alpha(0.2, BlendNormal))
			b.Line(r.X, r.Y, r.X+r.W, r.Y+r.H, NewPDFLineOpt().LineWidth(0.25).DrawColor(255, 0, 0))
			b.Line(r.X+r.W, r.Y, r.X, r.Y+r.H, NewPDFLineOpt().LineWidth(0.25).DrawColor(255, 0, 0))
		}
	}
}