/*
Extract and transform image into a compressed jpeg.
*/
package epubimageprocessor

import (
	"fmt"
	"image"
	"sync"

	epubimage "github.com/celogeek/go-comic-converter/v2/internal/epub/image"
	epubimagefilters "github.com/celogeek/go-comic-converter/v2/internal/epub/imagefilters"
	epuboptions "github.com/celogeek/go-comic-converter/v2/internal/epub/options"
	epubprogress "github.com/celogeek/go-comic-converter/v2/internal/epub/progress"
	epubzip "github.com/celogeek/go-comic-converter/v2/internal/epub/zip"
	"github.com/disintegration/gift"
)

type LoadedImage struct {
	Image    *epubimage.Image
	ZipImage *epubzip.ZipImage
}

type LoadedImages []*LoadedImage

func (l LoadedImages) Images() []*epubimage.Image {
	res := make([]*epubimage.Image, len(l))
	for i, v := range l {
		res[i] = v.Image
	}
	return res
}

type EPUBImageProcessor struct {
	*epuboptions.Options
}

func New(o *epuboptions.Options) *EPUBImageProcessor {
	return &EPUBImageProcessor{o}
}

// extract and convert images
func (e *EPUBImageProcessor) Load() (LoadedImages, error) {
	images := make(LoadedImages, 0)

	imageCount, imageInput, err := e.load()
	if err != nil {
		return nil, err
	}

	// dry run, skip convertion
	if e.Dry {
		for img := range imageInput {
			images = append(images, &LoadedImage{
				Image: &epubimage.Image{
					Id:   img.Id,
					Path: img.Path,
					Name: img.Name,
				},
			})
		}

		return images, nil
	}

	imageOutput := make(chan *LoadedImage)

	// processing
	bar := epubprogress.New(epubprogress.Options{
		Quiet:       e.Quiet,
		Max:         imageCount,
		Description: "Processing",
		CurrentJob:  1,
		TotalJob:    2,
	})
	wg := &sync.WaitGroup{}

	for i := 0; i < e.WorkersRatio(50); i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()

			for input := range imageInput {
				src := input.Image

				for part, dst := range e.transformImage(src, input.Id) {
					var raw image.Image
					if input.Id == 0 && part == 0 {
						raw = dst
					}

					img := &epubimage.Image{
						Id:         input.Id,
						Part:       part,
						Raw:        raw,
						Width:      dst.Bounds().Dx(),
						Height:     dst.Bounds().Dy(),
						IsCover:    input.Id == 0 && part == 0,
						IsBlank:    dst.Bounds().Dx() == 1 && dst.Bounds().Dy() == 1,
						DoublePage: part == 0 && src.Bounds().Dx() > src.Bounds().Dy(),
						Path:       input.Path,
						Name:       input.Name,
					}
					imageOutput <- &LoadedImage{
						Image:    img,
						ZipImage: epubzip.CompressImage(fmt.Sprintf("OEBPS/%s", img.ImgPath()), dst, e.Image.Quality),
					}
				}
			}
		}()
	}

	go func() {
		wg.Wait()
		close(imageOutput)
	}()

	for output := range imageOutput {
		if output.Image.Part == 0 {
			bar.Add(1)
		}
		if e.Image.NoBlankImage && output.Image.IsBlank {
			continue
		}
		images = append(images, output)
	}
	bar.Close()

	if len(images) == 0 {
		return nil, errNoImagesFound
	}

	return images, nil
}

// transform image into 1 or 3 images
// only doublepage with autosplit has 3 versions
func (e *EPUBImageProcessor) transformImage(src image.Image, srcId int) []image.Image {
	var filters, splitFilter []gift.Filter
	var images []image.Image

	// Lookup for margin if crop is enable or if we want to remove blank image
	if e.Image.Crop.Enabled || e.Image.NoBlankImage {
		f := epubimagefilters.AutoCrop(
			src,
			e.Image.Crop.Left,
			e.Image.Crop.Up,
			e.Image.Crop.Right,
			e.Image.Crop.Bottom,
		)

		// detect if blank image
		size := f.Bounds(src.Bounds())
		isBlank := size.Dx() == 0 && size.Dy() == 0

		// crop is enable or if blank image with noblankimage options
		if e.Image.Crop.Enabled || (e.Image.NoBlankImage && isBlank) {
			filters = append(filters, f)
			splitFilter = append(splitFilter, f)
		}
	}

	if e.Image.AutoRotate && src.Bounds().Dx() > src.Bounds().Dy() {
		filters = append(filters, gift.Rotate90())
	}

	if e.Image.Contrast != 0 {
		f := gift.Contrast(float32(e.Image.Contrast))
		filters = append(filters, f)
		splitFilter = append(splitFilter, f)
	}

	if e.Image.Brightness != 0 {
		f := gift.Brightness(float32(e.Image.Brightness))
		filters = append(filters, f)
		splitFilter = append(splitFilter, f)
	}

	filters = append(filters,
		epubimagefilters.Resize(e.Image.View.Width, e.Image.View.Height, gift.LanczosResampling),
		epubimagefilters.Pixel(),
	)

	// convert
	{
		g := gift.New(filters...)
		dst := image.NewGray(g.Bounds(src.Bounds()))
		g.Draw(dst, src)
		images = append(images, dst)
	}

	// auto split off
	if !e.Image.AutoSplitDoublePage {
		return images
	}

	// portrait, no need to split
	if src.Bounds().Dx() <= src.Bounds().Dy() {
		return images
	}

	// cover
	if e.Image.HasCover && srcId == 0 {
		return images
	}

	// convert double page
	for _, b := range []bool{e.Image.Manga, !e.Image.Manga} {
		g := gift.New(splitFilter...)
		g.Add(
			epubimagefilters.CropSplitDoublePage(b),
			epubimagefilters.Resize(e.Image.View.Width, e.Image.View.Height, gift.LanczosResampling),
		)
		dst := image.NewGray(g.Bounds(src.Bounds()))
		g.Draw(dst, src)
		images = append(images, dst)
	}

	return images
}

// create a title page with the cover
func (e *EPUBImageProcessor) CoverTitleData(img image.Image, title string) *epubzip.ZipImage {
	// Create a blur version of the cover
	g := gift.New(epubimagefilters.CoverTitle(title))
	dst := image.NewGray(g.Bounds(img.Bounds()))
	g.Draw(dst, img)

	return epubzip.CompressImage("OEBPS/Images/title.jpg", dst, e.Image.Quality)
}