package main

import (
	"bytes"
	"context"
	_ "embed"
	"errors"
	"flag"
	"fmt"
	"image/gif"
	"image/png"
	"net/url"
	"os"
	"path/filepath"
	"runtime/debug"
	"slices"
	"strconv"
	"strings"

	"github.com/gen2brain/cbconvert"
	"github.com/gen2brain/iup-go/iup"
)

//go:generate rsrc --ico dist/windows/icon.ico --arch amd64 -o main_windows_amd64.syso

//go:embed assets/logo.png
var appLogo []byte

//go:embed assets/loading.gif
var appLoading []byte

var appVersion string

var (
	index = -1
	files []cbconvert.File
)

func init() {
	if appVersion != "" {
		return
	}

	info, ok := debug.ReadBuildInfo()
	if !ok {
		return
	}

	if info.Main.Version != "" {
		appVersion = info.Main.Version
	}

	for _, kv := range info.Settings {
		if kv.Value == "" {
			continue
		}
		if kv.Key == "vcs.revision" {
			appVersion = kv.Value
			if len(appVersion) > 7 {
				appVersion = kv.Value[:7]
			}
		}
	}
}

func main() {
	parseFlags()

	iup.Open()
	defer iup.Close()

	iup.SetGlobal("UTF8MODE", "YES")
	iup.SetGlobal("UTF8MODE_FILE", "YES")

	img, _ := png.Decode(bytes.NewReader(appLogo))
	iup.ImageFromImage(img).SetHandle("logo")

	dlg := iup.Dialog(layout()).SetAttributes(fmt.Sprintf(`TITLE="CBconvert %s", ICON=logo`, appVersion)).SetHandle("dlg")

	dlg.SetCallback("POSTMESSAGE_CB", iup.PostMessageFunc(func(ih iup.Ihandle, s string, i int, p any) int {
		sp := strings.Split(s, ": ")
		if len(sp) > 1 {
			iup.MessageError(ih, fmt.Sprintf("%s\n\n%s", sp[0], strings.Join(sp[1:], ": ")))
		}

		return iup.DEFAULT
	}))

	dlg.SetCallback("RESIZE_CB", iup.ResizeFunc(func(ih iup.Ihandle, width, height int) int {
		iup.GetHandle("Preview").SetAttribute("IMAGE", "logo")
		iup.Refresh(ih)

		previewPost()

		return iup.DEFAULT
	}))

	iup.Map(dlg)
	setActive()

	iup.ShowXY(dlg, iup.CENTER, iup.CENTER)
	iup.MainLoop()
}

func parseFlags() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage: %s <command> [<flags>]\n\n", filepath.Base(os.Args[0]))
		fmt.Fprintf(os.Stderr, "\nCommands:\n")
		fmt.Fprintf(os.Stderr, "\n  version\n    \tPrint version\n\n")
	}

	flag.NewFlagSet("version", flag.ExitOnError)
	flag.Parse()

	if flag.NArg() >= 1 {
		if flag.Arg(0) == "version" {
			fmt.Println(filepath.Base(os.Args[0]), appVersion)
			os.Exit(0)
		} else {
			flag.Usage()
			os.Exit(1)
		}
	}
}

func options() cbconvert.Options {
	var opts cbconvert.Options
	opts.Recursive = iup.GetHandle("Recursive").GetAttribute("VALUE") == "ON"
	opts.NoRGB = iup.GetHandle("NoRGB").GetAttribute("VALUE") == "ON"
	opts.NoCover = iup.GetHandle("NoCover").GetAttribute("VALUE") == "ON"
	opts.Size = iup.GetHandle("Size").GetInt("VALUE")
	opts.OutDir = iup.GetHandle("OutDir").GetAttribute("VALUE")
	opts.Suffix = iup.GetHandle("Suffix").GetAttribute("VALUE")
	opts.NoConvert = iup.GetHandle("NoConvert").GetAttribute("VALUE") == "ON"
	opts.NoNonImage = iup.GetHandle("NoNonImage").GetAttribute("VALUE") == "ON"
	opts.Archive = strings.ToLower(iup.GetHandle("Archive").GetAttribute("VALUESTRING"))
	opts.Format = strings.ToLower(iup.GetHandle("Format").GetAttribute("VALUESTRING"))
	opts.Width = iup.GetHandle("Width").GetInt("VALUE")
	opts.Height = iup.GetHandle("Height").GetInt("VALUE")
	opts.Fit = iup.GetHandle("Fit").GetAttribute("VALUE") == "ON"
	opts.Filter = iup.GetHandle("Filter").GetInt("VALUE") - 1
	opts.Quality = iup.GetHandle("Quality").GetInt("VALUE")
	opts.Grayscale = iup.GetHandle("Grayscale").GetAttribute("VALUE") == "ON"
	opts.Brightness = iup.GetHandle("Brightness").GetInt("VALUE")
	opts.Contrast = iup.GetHandle("Contrast").GetInt("VALUE")
	opts.Rotate = iup.GetHandle("Rotate").GetInt("VALUESTRING")

	return opts
}

func setActive() {
	opts := options()
	count := iup.GetHandle("List").GetInt("COUNT")

	if count == 0 {
		iup.GetHandle("Remove").SetAttribute("ACTIVE", "NO")
		iup.GetHandle("RemoveAll").SetAttribute("ACTIVE", "NO")

		iup.GetHandle("Preview").SetAttribute("IMAGE", "logo")
		iup.GetHandle("PreviewInfo").SetAttribute("TITLE", "")
	} else {
		if index != -1 {
			iup.GetHandle("Remove").SetAttribute("ACTIVE", "YES")
		}
		iup.GetHandle("RemoveAll").SetAttribute("ACTIVE", "YES")
	}

	if opts.OutDir == "" {
		iup.GetHandle("Thumbnail").SetAttributes(`ACTIVE=NO`)
		iup.GetHandle("Cover").SetAttributes(`ACTIVE=NO`)
		iup.GetHandle("Convert").SetAttributes(`ACTIVE=NO`)
		if count > 0 {
			iup.GetHandle("Thumbnail").SetAttributes(`ACTIVE=NO, TIP="Set Output Directory"`)
			iup.GetHandle("Cover").SetAttributes(`ACTIVE=NO, TIP="Set Output Directory"`)
			iup.GetHandle("Convert").SetAttributes(`ACTIVE=NO, TIP="Set Output Directory"`)
		}
	} else {
		if count > 0 {
			iup.GetHandle("Thumbnail").SetAttributes(`ACTIVE=YES, TIP=""`)
			iup.GetHandle("Cover").SetAttributes(`ACTIVE=YES, TIP=""`)
			iup.GetHandle("Convert").SetAttributes(`ACTIVE=YES, TIP=""`)
		} else {
			iup.GetHandle("Thumbnail").SetAttributes(`ACTIVE=NO`)
			iup.GetHandle("Cover").SetAttributes(`ACTIVE=NO`)
			iup.GetHandle("Convert").SetAttributes(`ACTIVE=NO`)
		}
	}

	if opts.NoConvert {
		iup.GetHandle("VboxImage").SetAttribute("ACTIVE", "NO")
		iup.GetHandle("VboxTransform").SetAttribute("ACTIVE", "NO")
	} else {
		iup.GetHandle("VboxImage").SetAttribute("ACTIVE", "YES")
		iup.GetHandle("VboxTransform").SetAttribute("ACTIVE", "YES")
	}

	if (opts.Format == "jpeg" || opts.Format == "webp" || opts.Format == "avif" || opts.Format == "jxl") && !opts.NoConvert {
		iup.GetHandle("VboxQuality").SetAttribute("ACTIVE", "YES")
	} else {
		iup.GetHandle("VboxQuality").SetAttribute("ACTIVE", "NO")
	}

	if opts.Width != 0 && opts.Height != 0 && !opts.NoConvert {
		iup.GetHandle("Fit").SetAttribute("ACTIVE", "YES")
	} else {
		iup.GetHandle("Fit").SetAttribute("ACTIVE", "NO")
	}
}

func layout() iup.Ihandle {
	return iup.Vbox(
		iup.Hbox(
			preview(),
			iup.Hbox(
				iup.Vbox(
					list(),
					tabs(),
				).SetAttributes("NGAP=5"),
			).SetAttributes("NGAP=5"),
			buttons(),
		).SetAttributes("NGAP=5, NMARGIN=5x5"),

		iup.Label("").SetAttributes("SEPARATOR=HORIZONTAL"),
		status(),
	)
}

func list() iup.Ihandle {
	return iup.Vbox(
		iup.List().SetAttributes("EXPAND=YES, VISIBLECOLUMNS=16, VISIBLELINES=5").SetHandle("List").
			SetCallback("ACTION", iup.ListActionFunc(func(ih iup.Ihandle, text string, item int, state int) int {
				if state == 1 {
					index = item - 1
					setActive()
					previewPost()
				}

				return iup.DEFAULT
			})).
			SetCallback("DROPFILES_CB", iup.DropFilesFunc(func(ih iup.Ihandle, fileName string, num, x, y int) int {
				dec, err := url.QueryUnescape(fileName)
				if err != nil {
					iup.PostMessage(iup.GetHandle("dlg"), err.Error(), 0, 0)
					fmt.Println(err)

					return iup.DEFAULT
				}

				conv := cbconvert.New(options())

				fs, err := conv.Files([]string{dec})
				if err != nil {
					iup.PostMessage(iup.GetHandle("dlg"), err.Error(), 0, 0)
					fmt.Println(err)

					return iup.DEFAULT
				}

				for _, file := range fs {
					iup.SetAttribute(iup.GetHandle("List"), "APPENDITEM", fmt.Sprintf("%s (%s)", file.Name, file.SizeHuman))
					files = append(files, file)
				}

				setActive()

				return iup.DEFAULT
			})),
	)
}

func previewPost() {
	if index == -1 || len(files) == 0 {
		return
	}

	width, height := previewSize()
	iup.GetHandle("Loading").SetAttributes("VISIBLE=YES, START=YES")
	if strings.ToLower(iup.GetGlobal("DRIVER")) == "motif" {
		iup.GetHandle("Preview").SetAttribute("IMAGE", "")
	}

	opts := options()

	go func(opts cbconvert.Options) {
		conv := cbconvert.New(opts)

		var s string
		file := files[index]

		img, err := conv.Preview(file.Path, file.Stat, width, height)
		if err != nil {
			s = err.Error()
			fmt.Println(err)
		}

		iup.PostMessage(iup.GetHandle("Preview"), s, 0, img)
	}(opts)
}

func previewSize() (int, int) {
	var width, height int
	sp := strings.Split(iup.GetHandle("Preview").GetAttribute("RASTERSIZE"), "x")
	if len(sp) == 2 {
		width, _ = strconv.Atoi(sp[0])
		height, _ = strconv.Atoi(sp[1])
	}

	return width, height
}

func preview() iup.Ihandle {
	return iup.Frame(
		iup.Vbox(
			iup.Label("").SetAttributes("EXPAND=YES, ALIGNMENT=ACENTER, MINSIZE=400x, IMAGE=cover").SetHandle("Preview").
				SetCallback("POSTMESSAGE_CB", iup.PostMessageFunc(func(ih iup.Ihandle, s string, i int, p any) int {
					img := p.(cbconvert.Image)

					iup.GetHandle("Loading").SetAttributes("VISIBLE=NO, STOP=YES")

					if img.Image != nil && len(s) == 0 {
						iup.Destroy(iup.GetHandle("cover"))
						iup.ImageFromImage(img.Image).SetHandle("cover")

						ih.SetAttribute("IMAGE", "cover")
						iup.GetHandle("PreviewInfo").SetAttribute("TITLE", fmt.Sprintf("%s (%dx%d)", img.SizeHuman, img.Width, img.Height))
					} else {
						ih.SetAttribute("IMAGE", "logo")
						iup.GetHandle("PreviewInfo").SetAttribute("TITLE", "")

						sp := strings.Split(s, ": ")
						if len(sp) > 1 {
							iup.MessageError(ih, fmt.Sprintf("%s\n\n%s", sp[0], strings.Join(sp[1:], ": ")))
						}
					}

					return iup.DEFAULT
				})),
			iup.Label("").SetAttributes("EXPAND=HORIZONTAL, ALIGNMENT=ACENTER").SetHandle("PreviewInfo"),
		),
	)
}

func tabs() iup.Ihandle {
	vboxInput := iup.Vbox(
		iup.Toggle(" Recurse SubDirectories").SetHandle("Recursive").
			SetAttributes(`TIP="Process subdirectories recursively"`),
		iup.Toggle(" Only Grayscale Images").SetHandle("NoRGB").
			SetAttributes(`TIP="Do not convert images that have RGB colorspace"`),
		iup.Toggle(" Exclude Cover").SetHandle("NoCover").
			SetAttributes(`TIP="Do not convert the cover image"`),
		iup.Toggle(" Remove Non-Image Files from the Archive").SetHandle("NoNonImage").
			SetAttribute("TIP", "Remove .nfo, .xml, .txt files from the archive"),
		iup.Toggle(" Do not Transform or Convert Images").SetHandle("NoConvert").
			SetAttributes(`TIP="Copy images from archive or directory without modifications"`).
			SetCallback("VALUECHANGED_CB", iup.ValueChangedFunc(func(ih iup.Ihandle) int {
				setActive()

				return iup.DEFAULT
			})),
		iup.Vbox(
			iup.Label("Minimum Size (MiB):"),
			iup.Text().SetAttributes(`SPIN=YES, SPINMAX=2048, VISIBLECOLUMNS=4, MASK="/d*"`).SetHandle("Size").
				SetAttributes(`TIP="Process only files larger than minimum size"`),
		),
		iup.Space().SetAttributes("EXPAND=HORIZONTAL"),
	).SetHandle("VboxInput").SetAttributes("MARGIN=5x5, GAP=5")

	vboxOutput := iup.Vbox(
		iup.Vbox(
			iup.Label("Output Directory:"),
			iup.Text().SetAttributes("VISIBLECOLUMNS=16, MINSIZE=100x").SetHandle("OutDir").
				SetCallback("VALUECHANGED_CB", iup.ValueChangedFunc(func(ih iup.Ihandle) int {
					setActive()

					return iup.DEFAULT
				})),
			iup.Space().SetAttribute("SIZE", "5x0"),
			iup.Button("Browse...").SetAttributes("PADDING=DEFAULTBUTTONPADDING").
				SetCallback("ACTION", iup.ActionFunc(onOutputDirectory)),
		),
		iup.Vbox(
			iup.Label("Add Suffix to Output File:"),
			iup.Text().SetAttributes("VISIBLECOLUMNS=16, MINSIZE=100x").SetHandle("Suffix").
				SetAttribute("TIP", "Add suffix to filename, i.e. filename_suffix.cbz"),
		),
		iup.Vbox(
			iup.Label("Archive Format:"),
			iup.List().SetAttributes(map[string]string{
				"DROPDOWN": "YES",
				"VALUE":    "1",
				"1":        "ZIP",
				"2":        "TAR",
			}).SetHandle("Archive"),
		),
	).SetHandle("VboxOutput").SetAttributes("MARGIN=5x5, GAP=5")

	vboxImage := iup.Vbox(
		iup.Vbox(
			iup.Label("Format:"),
			iup.List().SetAttributes(map[string]string{
				"DROPDOWN": "YES",
				"VALUE":    "1",
				"1":        "JPEG",
				"2":        "PNG",
				"3":        "TIFF",
				"4":        "BMP",
				"5":        "WEBP",
				"6":        "AVIF",
				"7":        "JXL",
			}).SetHandle("Format").
				SetCallback("VALUECHANGED_CB", iup.ValueChangedFunc(func(ih iup.Ihandle) int {
					setActive()
					previewPost()

					return iup.DEFAULT
				})),
		),
		iup.Vbox(
			iup.Label("Size:"),
			iup.Hbox(
				iup.Text().SetAttributes(`CUEBANNER=" width", VISIBLECOLUMNS=4, MASK="/d*"`).SetHandle("Width").
					SetAttribute("TIP", "If one of, width or height is not set, the image aspect ratio is preserved").
					SetCallback("VALUECHANGED_CB", iup.ValueChangedFunc(func(ih iup.Ihandle) int {
						setActive()
						ih.SetAttribute("MYVALUE", ih.GetInt("VALUE"))

						return iup.DEFAULT
					})).
					SetCallback("KILLFOCUS_CB", iup.KillFocusFunc(func(ih iup.Ihandle) int {
						if ih.GetAttribute("MYVALUE") != "" {
							previewPost()
						}
						ih.SetAttribute("MYVALUE", "")

						return iup.DEFAULT
					})),
				iup.Label("x"),
				iup.Text().SetAttributes(`CUEBANNER=" height", VISIBLECOLUMNS=4, MASK="/d*"`).SetHandle("Height").
					SetAttribute("TIP", "If one of, width or height is not set, the image aspect ratio is preserved").
					SetCallback("VALUECHANGED_CB", iup.ValueChangedFunc(func(ih iup.Ihandle) int {
						setActive()
						ih.SetAttribute("MYVALUE", ih.GetInt("VALUE"))

						return iup.DEFAULT
					})).
					SetCallback("KILLFOCUS_CB", iup.KillFocusFunc(func(ih iup.Ihandle) int {
						if ih.GetAttribute("MYVALUE") != "" {
							previewPost()
						}
						ih.SetAttribute("MYVALUE", "")

						return iup.DEFAULT
					})),
			).SetAttributes("ALIGNMENT=ACENTER, MARGIN=0"),
		),
		iup.Vbox(
			iup.Toggle(" Best Fit").SetHandle("Fit").
				SetAttributes(`TIP="Best fit for required width and height"`),
		),
		iup.Vbox(
			iup.Label("Resize Filter:"),
			iup.List().SetAttributes(map[string]string{
				"DROPDOWN": "YES",
				"VALUE":    "3",
				"TIP":      "Linear is the bilinear filter, smooth and reasonably fast",
				"1":        "NearestNeighbor",
				"2":        "Box",
				"3":        "Linear",
				"4":        "MitchellNetravali",
				"5":        "CatmullRom",
				"6":        "Gaussian",
				"7":        "Lanczos",
			}).SetHandle("Filter").SetCallback("VALUECHANGED_CB", iup.ValueChangedFunc(onFilterChanged)),
		),
		iup.Vbox(
			iup.Hbox(
				iup.Label("Quality: "),
				iup.Label("75").SetHandle("LabelQuality"),
			).SetAttributes("MARGIN=0"),
			iup.Val("").SetAttributes(`MIN=0, MAX=100, VALUE=75, SHOWTICKS=10`).SetHandle("Quality").
				SetAttribute("TIP", "Quality affects JPEG, WEBP, AVIF and JXL").
				SetCallback("VALUECHANGED_CB", iup.ValueChangedFunc(func(ih iup.Ihandle) int {
					iup.GetHandle("LabelQuality").SetAttribute("TITLE", ih.GetInt("VALUE"))
					ih.SetAttribute("MYVALUE", ih.GetInt("VALUE"))

					return iup.DEFAULT
				})).
				SetCallback("KILLFOCUS_CB", iup.KillFocusFunc(func(ih iup.Ihandle) int {
					if ih.GetAttribute("MYVALUE") != "" {
						previewPost()
					}
					ih.SetAttribute("MYVALUE", "")

					return iup.DEFAULT
				})),
		).SetHandle("VboxQuality"),
		iup.Vbox(
			iup.Toggle(" Grayscale").SetHandle("Grayscale").
				SetAttributes(`TIP="Convert images to grayscale (monochromatic)"`).
				SetCallback("VALUECHANGED_CB", iup.ValueChangedFunc(func(ih iup.Ihandle) int {
					previewPost()

					return iup.DEFAULT
				})),
		),
	).SetHandle("VboxImage").SetAttributes("MARGIN=5x5, GAP=5")

	vboxTransform := iup.Vbox(
		iup.Vbox(
			iup.Hbox(
				iup.Label("Brightness: "),
				iup.Label("0").SetHandle("LabelBrightness"),
			).SetAttributes("ALIGNMENT=ACENTER, MARGIN=0"),
			iup.Val("").SetAttributes(`MIN=-100, MAX=100, VALUE=0, SHOWTICKS=10`).SetHandle("Brightness").
				SetAttributes(`TIP="Adjust the brightness of the images"`).
				SetCallback("VALUECHANGED_CB", iup.ValueChangedFunc(func(ih iup.Ihandle) int {
					iup.GetHandle("LabelBrightness").SetAttribute("TITLE", iup.GetHandle("Brightness").GetInt("VALUE"))
					ih.SetAttribute("MYVALUE", ih.GetInt("VALUE"))

					return iup.DEFAULT
				})).
				SetCallback("KILLFOCUS_CB", iup.KillFocusFunc(func(ih iup.Ihandle) int {
					if ih.GetAttribute("MYVALUE") != "" {
						previewPost()
					}
					ih.SetAttribute("MYVALUE", "")

					return iup.DEFAULT
				})),
		),
		iup.Vbox(
			iup.Hbox(
				iup.Label("Contrast: "),
				iup.Label("0").SetHandle("LabelContrast"),
			).SetAttributes("ALIGNMENT=ACENTER, MARGIN=0"),
			iup.Val("").SetAttributes(`MIN=-100, MAX=100, VALUE=0, SHOWTICKS=10`).SetHandle("Contrast").
				SetAttributes(`TIP="Adjust the contrast of the images"`).
				SetCallback("VALUECHANGED_CB", iup.ValueChangedFunc(func(ih iup.Ihandle) int {
					iup.GetHandle("LabelContrast").SetAttribute("TITLE", iup.GetHandle("Contrast").GetInt("VALUE"))
					ih.SetAttribute("MYVALUE", ih.GetInt("VALUE"))

					return iup.DEFAULT
				})).
				SetCallback("KILLFOCUS_CB", iup.KillFocusFunc(func(ih iup.Ihandle) int {
					if ih.GetAttribute("MYVALUE") != "" {
						previewPost()
					}
					ih.SetAttribute("MYVALUE", "")

					return iup.DEFAULT
				})),
		),
		iup.Vbox(
			iup.Label("Rotate:"),
			iup.List().SetAttributes(map[string]string{
				"DROPDOWN": "YES",
				"VALUE":    "1",
				"1":        "0",
				"2":        "90",
				"3":        "180",
				"4":        "270",
			}).SetHandle("Rotate").
				SetCallback("VALUECHANGED_CB", iup.ValueChangedFunc(func(ih iup.Ihandle) int {
					previewPost()

					return iup.DEFAULT
				})),
		),
	).SetHandle("VboxTransform").SetAttributes("MARGIN=5x5, GAP=5")

	return iup.Tabs(
		vboxInput.SetAttributes("TABTITLE=Input"),
		vboxOutput.SetAttributes("TABTITLE=Output"),
		vboxImage.SetAttributes("TABTITLE=Image"),
		vboxTransform.SetAttributes("TABTITLE=Transform"),
	).SetHandle("Tabs").SetAttributes("MINSIZE=320x400, EXPAND=HORIZONTAL, MULTILINE=YES")
}

func buttons() iup.Ihandle {
	return iup.Vbox(
		iup.Frame(
			iup.Vbox(
				iup.Button("Add &Files...").SetHandle("AddFiles").SetAttributes("EXPAND=HORIZONTAL, PADDING=DEFAULTBUTTONPADDING").
					SetCallback("ACTION", iup.ActionFunc(onAddFiles)),
				iup.Button("Add &Dir...").SetHandle("AddDir").SetAttributes("EXPAND=HORIZONTAL, PADDING=DEFAULTBUTTONPADDING").
					SetCallback("ACTION", iup.ActionFunc(onAddDir)),
				iup.Button("Remove").SetHandle("Remove").SetAttributes("EXPAND=HORIZONTAL, PADDING=DEFAULTBUTTONPADDING").
					SetCallback("ACTION", iup.ActionFunc(onRemove)),
				iup.Button("Remove All").SetHandle("RemoveAll").SetAttributes("EXPAND=HORIZONTAL, PADDING=DEFAULTBUTTONPADDING").
					SetCallback("ACTION", iup.ActionFunc(onRemoveAll)),
			).SetAttributes("NGAP=5"),
		),
		iup.Frame(
			iup.Vbox(
				iup.Button("Thumbnail").SetHandle("Thumbnail").SetAttributes("EXPAND=HORIZONTAL, PADDING=DEFAULTBUTTONPADDING").
					SetCallback("ACTION", iup.ActionFunc(onThumbnail)),
				iup.Button("Cover").SetHandle("Cover").SetAttributes("EXPAND=HORIZONTAL, PADDING=DEFAULTBUTTONPADDING").
					SetCallback("ACTION", iup.ActionFunc(onCover)),
			).SetAttributes("NGAP=5"),
		),
		iup.Frame(
			iup.Vbox(
				iup.Button("&Convert").SetHandle("Convert").SetAttributes("EXPAND=HORIZONTAL, PADDING=DEFAULTBUTTONPADDING").
					SetCallback("ACTION", iup.ActionFunc(onConvert)),
			),
		),
	).SetHandle("Buttons").SetAttributes("ALIGNMENT=ACENTER, NGAP=10")
}

func status() iup.Ihandle {
	return iup.Hbox(
		loading(),
		iup.Fill(),
		iup.Label("File 1 of 1").SetHandle("LabelStatus1").SetAttributes("VISIBLE=NO"),
		iup.Space().SetAttribute("SIZE", "5x0"),
		iup.Label("(000/000)").SetHandle("LabelStatus2").SetAttributes("VISIBLE=NO"),
		iup.Space().SetAttribute("SIZE", "5x0"),
		iup.ProgressBar().SetAttributes("RASTERSIZE=200x15, VISIBLE=NO").SetHandle("ProgressBar").
			SetCallback("POSTMESSAGE_CB", iup.PostMessageFunc(func(ih iup.Ihandle, s string, i int, p any) int {
				switch s {
				case "convert":
					conv := p.(*cbconvert.Converter)
					ih.SetAttributes("VALUE=0, VISIBLE=YES")
					ih.SetAttribute("MAX", conv.Ncontents)

					iup.GetHandle("List").SetAttributes("ACTIVE=NO")
					iup.GetHandle("Tabs").SetAttributes("ACTIVE=NO")
					iup.GetHandle("Buttons").SetAttributes("ACTIVE=NO")

					iup.GetHandle("LabelStatus1").SetAttribute("TITLE", fmt.Sprintf("File %d of %d", conv.CurrFile, conv.Nfiles))
					iup.GetHandle("LabelStatus1").SetAttributes("VISIBLE=YES")
					iup.GetHandle("LabelStatus2").SetAttributes("VISIBLE=YES")

					iup.Refresh(iup.GetHandle("StatusBar"))
				case "start":
					conv := p.(*cbconvert.Converter)
					ih.SetAttributes("VALUE=0, VISIBLE=YES")
					ih.SetAttribute("MAX", conv.Nfiles)

					iup.GetHandle("List").SetAttributes("ACTIVE=NO")
					iup.GetHandle("Tabs").SetAttributes("ACTIVE=NO")
					iup.GetHandle("Buttons").SetAttributes("ACTIVE=NO")

					iup.GetHandle("LabelStatus2").SetAttributes("VISIBLE=YES")
				case "progress":
					conv := p.(*cbconvert.Converter)
					ih.SetAttribute("VALUE", conv.CurrContent)
					iup.GetHandle("LabelStatus2").SetAttribute("TITLE", fmt.Sprintf("(%03d/%03d)", conv.CurrContent, conv.Ncontents))

					iup.Refresh(iup.GetHandle("StatusBar"))
				case "progress2":
					conv := p.(*cbconvert.Converter)
					ih.SetAttribute("VALUE", conv.CurrFile)
					iup.GetHandle("LabelStatus2").SetAttribute("TITLE", fmt.Sprintf("(%03d/%03d)", conv.CurrFile, conv.Nfiles))

					iup.Refresh(iup.GetHandle("StatusBar"))
				case "finish":
					iup.GetHandle("List").SetAttributes("ACTIVE=YES")
					iup.GetHandle("Tabs").SetAttributes("ACTIVE=YES")
					iup.GetHandle("Buttons").SetAttributes("ACTIVE=YES")

					iup.GetHandle("LabelStatus1").SetAttributes(`TITLE="", VISIBLE=NO`)
					iup.GetHandle("LabelStatus2").SetAttributes(`TITLE="", VISIBLE=NO`)
					ih.SetAttributes("VALUE=0, VISIBLE=NO")

					iup.Refresh(iup.GetHandle("StatusBar"))

					iup.GetHandle("dlg").SetCallback("K_ANY", nil)
					iup.GetHandle("dlg").SetCallback("CLOSE_CB", nil)
				}

				return iup.DEFAULT
			})),
		iup.Space().SetAttribute("SIZE", "5x0"),
	).SetAttributes("ALIGNMENT=ACENTER, NMARGIN=5x5").SetHandle("StatusBar")
}

func loading() iup.Ihandle {
	img, _ := gif.DecodeAll(bytes.NewReader(appLoading))
	animation := iup.User()

	for idx, i := range img.Image {
		name := fmt.Sprintf("Loading%d", idx)
		iup.ImageFromImage(i).SetHandle(name)
		iup.Append(animation, iup.GetHandle(name))
	}

	return iup.AnimatedLabel(animation).SetAttributes("VISIBLE=NO").SetHandle("Loading")
}

func onAddFiles(ih iup.Ihandle) int {
	args, err := fileDlg("Add Files", true, false)
	if err != nil {
		iup.PostMessage(iup.GetHandle("dlg"), err.Error(), 0, 0)
		fmt.Println(err)

		return iup.DEFAULT
	}

	if len(args) > 0 {
		conv := cbconvert.New(options())

		fs, err := conv.Files(args)
		if err != nil {
			iup.PostMessage(iup.GetHandle("dlg"), err.Error(), 0, 0)
			fmt.Println(err)

			return iup.DEFAULT
		}

		for _, file := range fs {
			iup.SetAttribute(iup.GetHandle("List"), "APPENDITEM", fmt.Sprintf("%s (%s)", file.Name, file.SizeHuman))
			files = append(files, file)
		}

		setActive()
	}

	return iup.DEFAULT
}

func onAddDir(ih iup.Ihandle) int {
	args, err := fileDlg("Add Directory", false, true)
	if err != nil {
		iup.PostMessage(iup.GetHandle("dlg"), err.Error(), 0, 0)
		fmt.Println(err)

		return iup.DEFAULT
	}

	if len(args) > 0 {
		conv := cbconvert.New(options())

		fs, err := conv.Files(args)
		if err != nil {
			iup.PostMessage(iup.GetHandle("dlg"), err.Error(), 0, 0)
			fmt.Println(err)

			return iup.DEFAULT
		}

		for _, file := range fs {
			iup.SetAttribute(iup.GetHandle("List"), "APPENDITEM", fmt.Sprintf("%s (%s)", file.Name, file.SizeHuman))
			files = append(files, file)
		}

		setActive()
	}

	return iup.DEFAULT
}

func onRemove(ih iup.Ihandle) int {
	if index == -1 || len(files) == 0 {
		return iup.IGNORE
	}

	if len(files) == 1 {
		files = make([]cbconvert.File, 0)
	} else {
		files = slices.Delete(files, index, index)
	}

	iup.GetHandle("List").SetAttribute("REMOVEITEM", iup.GetHandle("List").GetAttribute("VALUE"))
	setActive()

	return iup.DEFAULT
}

func onRemoveAll(ih iup.Ihandle) int {
	index = -1
	files = make([]cbconvert.File, 0)

	iup.GetHandle("List").SetAttribute("REMOVEITEM", "ALL")
	setActive()

	return iup.DEFAULT
}

func onThumbnail(ih iup.Ihandle) int {
	conv := cbconvert.New(options())
	conv.Nfiles = len(files)

	conv.OnProgress = func() {
		iup.PostMessage(iup.GetHandle("ProgressBar"), "progress2", 0, conv)
	}

	var canceled = false
	conv.OnCancel = func() {
		canceled = true
	}

	iup.GetHandle("dlg").SetCallback("K_ANY", iup.KAnyFunc(func(ih iup.Ihandle, c int) int {
		if c == iup.K_ESC {
			conv.Cancel()
		}

		return iup.DEFAULT
	}))

	iup.PostMessage(iup.GetHandle("ProgressBar"), "start", 0, conv)

	go func(c *cbconvert.Converter) {
		for _, file := range files {
			if canceled {
				break
			}

			if err := c.Thumbnail(file.Path, file.Stat); err != nil {
				iup.PostMessage(iup.GetHandle("dlg"), err.Error(), 0, 0)
				fmt.Println(err)

				continue
			}
		}

		iup.PostMessage(iup.GetHandle("ProgressBar"), "finish", 0, 0)
	}(conv)

	return iup.DEFAULT
}

func onCover(ih iup.Ihandle) int {
	conv := cbconvert.New(options())
	conv.Nfiles = len(files)

	conv.OnProgress = func() {
		iup.PostMessage(iup.GetHandle("ProgressBar"), "progress2", 0, conv)
	}

	var canceled = false
	conv.OnCancel = func() {
		canceled = true
	}

	iup.GetHandle("dlg").SetCallback("K_ANY", iup.KAnyFunc(func(ih iup.Ihandle, c int) int {
		if c == iup.K_ESC {
			conv.Cancel()
		}

		return iup.DEFAULT
	}))

	iup.PostMessage(iup.GetHandle("ProgressBar"), "start", 0, conv)

	go func(c *cbconvert.Converter) {
		for _, file := range files {
			if canceled {
				break
			}

			if err := c.Cover(file.Path, file.Stat); err != nil {
				iup.PostMessage(iup.GetHandle("dlg"), err.Error(), 0, 0)
				fmt.Println(err)

				continue
			}
		}

		iup.PostMessage(iup.GetHandle("ProgressBar"), "finish", 0, 0)
	}(conv)

	return iup.DEFAULT
}

func onConvert(ih iup.Ihandle) int {
	conv := cbconvert.New(options())
	conv.Nfiles = len(files)

	conv.OnStart = func() {
		iup.PostMessage(iup.GetHandle("ProgressBar"), "convert", 0, conv)
	}

	conv.OnProgress = func() {
		iup.PostMessage(iup.GetHandle("ProgressBar"), "progress", 0, conv)
	}

	iup.GetHandle("dlg").SetCallback("K_ANY", iup.KAnyFunc(func(ih iup.Ihandle, c int) int {
		if c == iup.K_ESC {
			conv.Cancel()
		}

		return iup.DEFAULT
	})).SetCallback("CLOSE_CB", iup.CloseFunc(func(ih iup.Ihandle) int {
		if err := os.RemoveAll(conv.Workdir); err != nil {
			fmt.Println(err)
		}

		return iup.DEFAULT
	}))

	go func(c *cbconvert.Converter) {
		for _, file := range files {
			if err := c.Convert(file.Path, file.Stat); err != nil {
				if errors.Is(err, context.Canceled) {
					if err := os.RemoveAll(c.Workdir); err != nil {
						fmt.Println(err)
					}

					break
				}

				iup.PostMessage(iup.GetHandle("dlg"), err.Error(), 0, 0)
				fmt.Println(err)

				if err := os.RemoveAll(c.Workdir); err != nil {
					fmt.Println(err)
				}

				continue
			}
		}

		iup.PostMessage(iup.GetHandle("ProgressBar"), "finish", 0, 0)
	}(conv)

	return iup.DEFAULT
}

func onOutputDirectory(ih iup.Ihandle) int {
	args, err := fileDlg("Output Directory", false, true)
	if err != nil {
		iup.PostMessage(iup.GetHandle("dlg"), err.Error(), 0, 0)
		fmt.Println(err)

		return iup.DEFAULT
	}

	if len(args) == 1 {
		iup.GetHandle("OutDir").SetAttribute("VALUE", args[0])
	}

	setActive()

	return iup.DEFAULT
}

func onFilterChanged(ih iup.Ihandle) int {
	switch ih.GetInt("VALUE") {
	case 1:
		ih.SetAttribute("TIP", "NearestNeighbor is the fastest resampling filter, no antialiasing")
	case 2:
		ih.SetAttribute("TIP", "Box filter (averaging pixels)")
	case 3:
		ih.SetAttribute("TIP", "Linear is the bilinear filter, smooth and reasonably fast")
	case 4:
		ih.SetAttribute("TIP", "MitchellNetravali is a smooth bicubic filter")
	case 5:
		ih.SetAttribute("TIP", "CatmullRom is a sharp bicubic filter")
	case 6:
		ih.SetAttribute("TIP", "Gaussian is a blurring filter that uses gaussian function, useful for noise removal")
	case 7:
		ih.SetAttribute("TIP", "Lanczos is a high-quality resampling filter, it's slower than cubic filters")
	}

	previewPost()

	return iup.DEFAULT
}
