package task

import (
	"context"
	"encoding/json"
	"fmt"
	"git.kuschku.de/justjanne/imghost-frontend/environment"
	"git.kuschku.de/justjanne/imghost-frontend/repo"
	"git.kuschku.de/justjanne/imghost-frontend/util"
	"github.com/hibiken/asynq"
	"github.com/justjanne/imgconv"
	"gopkg.in/gographics/imagick.v2/imagick"
	"io/ioutil"
	"strings"
)

type ImageProcessor struct {
	env environment.BackendEnvironment
}

func NewImageProcessor(env environment.BackendEnvironment) *ImageProcessor {
	return &ImageProcessor{
		env,
	}
}

func (processor *ImageProcessor) ProcessTask(ctx context.Context, task *asynq.Task) (err error) {
	var payload ImageResizePayload
	if err = json.Unmarshal(task.Payload(), &payload); err != nil {
		println("Could not unmarshal task")
		println(err.Error())
		return
	}

	println("parsed task: " + payload.ImageId)

	if err = processor.env.Repositories.Images.UpdateState(payload.ImageId, repo.StateInProgress); err != nil {
		println("failed to set image state: " + payload.ImageId)
		println(err.Error())
		return
	}

	wand := imagick.NewMagickWand()
	defer wand.Destroy()

	sourceFile, err := ioutil.TempFile("", payload.ImageId+"*."+payload.Extension)
	if err != nil {
		println("failed to create temp file: " + payload.ImageId)
		println(err.Error())
		_ = processor.env.Repositories.Images.UpdateState(payload.ImageId, repo.StateError)
		return
	}
	err = processor.env.Storage.DownloadFile(
		ctx,
		processor.env.Configuration.Storage.ConversionBucket,
		payload.ImageId,
		sourceFile)
	if err != nil {
		println("failed to download file: " + sourceFile.Name())
		println(err.Error())
		_ = processor.env.Repositories.Images.UpdateState(payload.ImageId, repo.StateError)
		return
	}
	if err = wand.ReadImage(sourceFile.Name()); err != nil {
		println("failed to read file: " + sourceFile.Name())
		println(err.Error())
		_ = processor.env.Repositories.Images.UpdateState(payload.ImageId, repo.StateError)
		return
	}
	var originalImage imgconv.ImageHandle
	if originalImage, err = imgconv.NewImage(wand); err != nil {
		println("failed to load file: " + sourceFile.Name())
		println(err.Error())
		_ = processor.env.Repositories.Images.UpdateState(payload.ImageId, repo.StateError)
		return
	}

	supportedMetadata := map[string]bool{
		"Make":                  true,
		"Model":                 true,
		"DateTime":              true,
		"DateTimeDigitized":     true,
		"DateTimeOriginal":      true,
		"DigitalZoomRatio":      true,
		"ExposureBiasValue":     true,
		"ExposureMode":          true,
		"ExposureProgram":       true,
		"ExposureTime":          true,
		"FNumber":               true,
		"Flash":                 true,
		"FlashEnergy":           true,
		"FocalLength":           true,
		"FocalLengthIn35mmFilm": true,
		"ISOSpeedRatings":       true,
		"LightSource":           true,
		"MeteringMode":          true,
		"WhiteBalance":          true,
		"Contrast":              true,
		"Sharpness":             true,
		"SubjectDistance":       true,
		"SubjectDistanceRange":  true,
		"Software":              true,
		"Copyright":             true,
	}
	metadata := make(map[string]string)
	for _, key := range wand.GetImageProperties("exif:*") {
		if strings.HasPrefix(key, "exif:thumbnail:") {
			continue
		}
		trimmedKey := strings.TrimPrefix(key, "exif:")
		if !supportedMetadata[trimmedKey] {
			continue
		}
		metadata[trimmedKey] = wand.GetImageProperty(key)
	}
	err = processor.env.Repositories.ImageMetadata.Update(payload.ImageId, metadata)
	if err != nil {
		println("failed to write metadata: " + payload.ImageId)
		println(err.Error())
		_ = processor.env.Repositories.Images.UpdateState(payload.ImageId, repo.StateError)
		return
	}

	err = util.LaunchGoroutines(len(payload.Sizes), func(index int) error {
		outputFile, err := ioutil.TempFile("", payload.ImageId+"*.png")
		if err != nil {
			return err
		}

		size := payload.Sizes[index]
		image := originalImage.CloneImage()
		if err := image.Crop(size.Size); err != nil {
			return err
		}
		if err := image.Resize(size.Size); err != nil {
			return err
		}
		if err := image.Write(payload.Quality, outputFile); err != nil {
			return err
		}
		if err := processor.env.Storage.UploadFile(
			ctx,
			processor.env.Configuration.Storage.ImageBucket,
			fmt.Sprintf("%s%s", payload.ImageId, size.Suffix),
			"image/png",
			outputFile); err != nil {
			return err
		}
		return nil
	})
	if err != nil {
		println("failed to convert image file")
		println(err.Error())
		_ = processor.env.Repositories.Images.UpdateState(payload.ImageId, repo.StateError)
		return
	}

	err = processor.env.Storage.DeleteFiles(
		ctx,
		processor.env.Configuration.Storage.ConversionBucket,
		payload.ImageId,
	)
	if err != nil {
		println("failed to delete temp file: " + payload.ImageId)
		println(err.Error())
		_ = processor.env.Repositories.Images.UpdateState(payload.ImageId, repo.StateError)
		return
	}

	if err = processor.env.Repositories.Images.UpdateState(payload.ImageId, repo.StateDone); err != nil {
		return
	}

	return
}