package windows

import (
	"fmt"
	"regexp"
	"strconv"

	"git.kirsle.net/SketchyMaze/doodle/pkg/balance"
	"git.kirsle.net/SketchyMaze/doodle/pkg/enum"
	"git.kirsle.net/SketchyMaze/doodle/pkg/level"
	"git.kirsle.net/SketchyMaze/doodle/pkg/log"
	"git.kirsle.net/SketchyMaze/doodle/pkg/modal"
	"git.kirsle.net/SketchyMaze/doodle/pkg/native"
	"git.kirsle.net/SketchyMaze/doodle/pkg/shmem"
	magicform "git.kirsle.net/SketchyMaze/doodle/pkg/uix/magic-form"
	"git.kirsle.net/SketchyMaze/doodle/pkg/wallpaper"
	"git.kirsle.net/go/render"
	"git.kirsle.net/go/ui"
	"github.com/google/uuid"
)

// AddEditLevel is the "Create New Level & Edit Level Properties" window
type AddEditLevel struct {
	Supervisor *ui.Supervisor
	Engine     render.Engine

	// Editing settings for an existing level?
	EditLevel *level.Level

	// Show the "New Doodad" tab by default?
	NewDoodad bool

	// Callback functions.
	OnChangePageTypeAndWallpaper func(pageType level.PageType, wallpaper string)
	OnCreateNewLevel             func(*level.Level)
	OnCreateNewDoodad            func(width, height int)
	OnUpdateScreenshot           func() error
	OnReload                     func()
	OnCancel                     func()
}

// NewAddEditLevel initializes the window.
func NewAddEditLevel(config AddEditLevel) *ui.Window {
	// Default options.
	var (
		title = "New Drawing"
	)

	// Given a level to edit?
	if config.EditLevel != nil {
		title = "Level Properties"
	}

	window := ui.NewWindow(title)
	window.SetButtons(ui.CloseButton)
	window.Configure(ui.Config{
		Width:      400,
		Height:     290,
		Background: render.Grey,
	})

	// Tabbed UI for New Level or New Doodad.
	tabframe := ui.NewTabFrame("Level Tabs")
	window.Pack(tabframe, ui.Pack{
		Side:   ui.N,
		Fill:   true,
		Expand: true,
	})

	// Add the tabs.
	config.setupLevelFrame(tabframe) // Level Properties (always)
	if config.EditLevel == nil {
		// New Doodad properties (New window only)
		config.setupDoodadFrame(tabframe)
	} else {
		// Additional Level tabs (existing level only)
		config.setupGameRuleFrame(tabframe)
		config.setupScreenshotFrame(tabframe)
		config.setupAdvancedFrame(tabframe)
	}

	tabframe.Supervise(config.Supervisor)

	// Show the doodad tab?
	if config.NewDoodad {
		tabframe.SetTab("doodad")
	}

	window.Hide()
	return window
}

// Creates the Create/Edit Level tab ("index").
func (config AddEditLevel) setupLevelFrame(tf *ui.TabFrame) {
	// Default options.
	var (
		tabLabel     = "New Level"
		newPageType  = level.Bounded.String()
		newWallpaper = "notebook.png"
		paletteName  = level.DefaultPaletteNames[0]
		isNewLevel   = config.EditLevel == nil

		// Default text for the Palette drop-down for already-existing levels.
		// (needs --experimental feature flag to enable the UI).
		textCurrentPalette = "Keep current palette"

		// For NEW levels, if a custom wallpaper is selected from disk, cache
		// it in these vars. For pre-existing levels, the wallpaper updates
		// immediately in the live config.EditLevel object.
		newWallpaperB64 string
	)

	// Given a level to edit?
	if !isNewLevel {
		tabLabel = "Properties"
		newPageType = config.EditLevel.PageType.String()
		newWallpaper = config.EditLevel.Wallpaper
		paletteName = textCurrentPalette
	}

	frame := tf.AddTab("index", ui.NewLabel(ui.Label{
		Text: tabLabel,
		Font: balance.TabFont,
	}))

	/******************
	 * Frame for selecting Page Type
	 ******************/

	// Selected "Page Type" property.
	var pageType = level.Bounded
	if !isNewLevel {
		pageType = config.EditLevel.PageType
	}

	form := magicform.Form{
		Supervisor: config.Supervisor,
		Engine:     config.Engine,
		Vertical:   true,
		LabelWidth: 120,
		PadY:       2,
	}
	fields := []magicform.Field{
		{
			Label: "Page type:",
			Font:  balance.UIFont,
			Options: []magicform.Option{
				{
					Label: "Bounded",
					Value: level.Bounded,
				},
				{
					Label: "Unbounded",
					Value: level.Unbounded,
				},
				{
					Label: "No Negative Space",
					Value: level.NoNegativeSpace,
				},
			},
			SelectValue: pageType,
			OnSelect: func(v interface{}) {
				value, _ := v.(level.PageType)
				newPageType = value.String() // for the "New" screen background
				config.OnChangePageTypeAndWallpaper(value, newWallpaper)
			},
		},
	}

	/******************
	 * Wallpaper settings
	 ******************/

	var selectedWallpaper = "notebook.png"
	if config.EditLevel != nil {
		selectedWallpaper = config.EditLevel.Wallpaper
	}

	fields = append(fields, []magicform.Field{
		{
			Label:       "Wallpaper:",
			Font:        balance.UIFont,
			SelectValue: selectedWallpaper,
			Options:     balance.Wallpapers,
			OnSelect: func(v interface{}) {
				if filename, ok := v.(string); ok {
					// Picking the Custom option?
					if filename == balance.CustomWallpaperFilename {
						filename, err := native.OpenFile("Choose a custom wallpaper:", "*.png *.jpg *.gif")
						if err == nil {
							b64data, err := wallpaper.FileToB64(filename)
							if err != nil {
								shmem.Flash("Error loading wallpaper: %s", err)
								return
							}

							// If editing a level, apply the update straight away.
							if config.EditLevel != nil {
								config.EditLevel.SetFile(balance.CustomWallpaperEmbedPath, []byte(b64data))
								newWallpaper = balance.CustomWallpaperFilename

								// Trigger the page type change to the caller.
								if pageType, ok := level.PageTypeFromString(newPageType); ok {
									config.OnChangePageTypeAndWallpaper(pageType, balance.CustomWallpaperFilename)
								}
							} else {
								// Hold onto the new wallpaper until the level is created.
								newWallpaper = balance.CustomWallpaperFilename
								newWallpaperB64 = b64data
							}
						}
						return
					}

					if pageType, ok := level.PageTypeFromString(newPageType); ok {
						config.OnChangePageTypeAndWallpaper(pageType, filename)
						newWallpaper = filename
					}
				}
			},
		},
	}...)

	/******************
	 * Frame for picking a default color palette.
	 ******************/

	// For new level or --experimental only.
	if config.EditLevel == nil || balance.Feature.ChangePalette {
		var (
			palettes = []magicform.Option{}
		)

		if config.EditLevel != nil {
			palettes = append(palettes, []magicform.Option{
				{
					Label: paletteName, // "Keep current palette"
					Value: paletteName,
				},
				{
					Separator: true,
				},
			}...)
		}

		for _, palName := range level.DefaultPaletteNames {
			palettes = append(palettes, magicform.Option{
				Label: palName,
				Value: palName,
			})
		}

		// Add form fields.
		fields = append(fields, []magicform.Field{
			{
				Label:   "Palette:",
				Font:    balance.UIFont,
				Options: palettes,
				OnSelect: func(v interface{}) {
					value, _ := v.(string)
					paletteName = value
				},
			},
		}...)
	}

	/******************
	 * Extended options for editing existing level (vs. Create New screen)
	 ******************/

	if config.EditLevel != nil {
		var (
			levelSizeStr    = fmt.Sprintf("%dx%d", config.EditLevel.MaxWidth, config.EditLevel.MaxHeight)
			levelSizeRegexp = regexp.MustCompile(`^(\d+)x(\d+)$`)
		)
		fields = append(fields, []magicform.Field{
			{
				Label:        "Limits (bounded):",
				Font:         balance.UIFont,
				TextVariable: &levelSizeStr,
				OnClick: func() {
					shmem.Prompt(fmt.Sprintf("Enter new limits in WxH format or [%s]: ", levelSizeStr), func(answer string) {
						if answer == "" {
							return
						}

						match := levelSizeRegexp.FindStringSubmatch(answer)
						if match == nil {
							return
						}

						levelSizeStr = match[0]
						width, _ := strconv.Atoi(match[1])
						height, _ := strconv.Atoi(match[2])

						config.EditLevel.MaxWidth = int64(width)
						config.EditLevel.MaxHeight = int64(height)
					})
				},
			},
			{
				Label: "Metadata",
				Font:  balance.LabelFont,
			},
			{
				Label:        "Title:",
				Font:         balance.UIFont,
				TextVariable: &config.EditLevel.Title,
				PromptUser: func(answer string) {
					config.EditLevel.Title = answer
				},
			},
			{
				Label:        "Author:",
				Font:         balance.UIFont,
				TextVariable: &config.EditLevel.Author,
				PromptUser: func(answer string) {
					config.EditLevel.Author = answer
				},
			},
		}...)
	}

	// The confirm/cancel buttons.
	var okLabel = "Apply"
	if config.EditLevel == nil {
		okLabel = "Continue"
	}
	fields = append(fields, []magicform.Field{
		{
			Buttons: []magicform.Field{
				{
					ButtonStyle: &balance.ButtonPrimary,
					Label:       okLabel,
					Font:        balance.UIFont,
					OnClick: func() {
						// Is it a NEW level?
						if config.EditLevel == nil {
							shmem.Flash("Create new map with %s page type and %s wallpaper", newPageType, newWallpaper)
							pageType, ok := level.PageTypeFromString(newPageType)
							if !ok {
								shmem.Flash("Invalid Page Type '%s'", newPageType)
								return
							}

							lvl := level.New()
							lvl.Palette = level.DefaultPalettes[paletteName]
							lvl.Wallpaper = newWallpaper
							lvl.PageType = pageType

							// Was a custom wallpaper selected for our NEW level?
							if lvl.Wallpaper == balance.CustomWallpaperFilename && len(newWallpaperB64) > 0 {
								lvl.SetFile(balance.CustomWallpaperEmbedPath, []byte(newWallpaperB64))
							}

							if config.OnCreateNewLevel != nil {
								config.OnCreateNewLevel(lvl)
							} else {
								shmem.FlashError("OnCreateNewLevel not attached")
							}
						} else {
							// Editing an existing level.

							// If we're editing a level, did we select a new palette?
							// Warn the user about if they want to change palettes.
							if paletteName != textCurrentPalette {
								modal.Confirm(
									"Are you sure you want to change the level palette?\n" +
										"Existing pixels drawn on your level may change, and\n" +
										"if the new palette is smaller, some pixels may be\n" +
										"lost from your level. OK to continue?",
								).WithTitle("Change Level Palette").Then(func() {
									// Install the new level palette.
									config.EditLevel.ReplacePalette(level.DefaultPalettes[paletteName])
									if config.OnReload != nil {
										config.OnReload()
									}
								})
								return
							}

							config.OnCancel()
						}
					},
				},
				{
					Label: "Cancel",
					Font:  balance.UIFont,
					OnClick: func() {
						config.OnCancel()
					},
				},
			},
		},
	}...)

	form.Create(frame, fields)
}

// Creates the "New Doodad" frame.
func (config AddEditLevel) setupDoodadFrame(tf *ui.TabFrame) {
	// Default options.
	var (
		doodadWidth  = 64
		doodadHeight = doodadWidth
	)

	frame := tf.AddTab("doodad", ui.NewLabel(ui.Label{
		Text: "New Doodad",
		Font: balance.TabFont,
	}))

	/******************
	 * Frame for selecting Page Type
	 ******************/

	var sizeOptions = []magicform.Option{
		{Label: "32", Value: 32},
		{Label: "64", Value: 64},
		{Label: "96", Value: 96},
		{Label: "128", Value: 128},
		{Label: "200", Value: 200},
		{Label: "256", Value: 256},
		{Label: "Custom...", Value: 0},
	}

	form := magicform.Form{
		Supervisor: config.Supervisor,
		Engine:     config.Engine,
		Vertical:   true,
		LabelWidth: 90,
	}
	form.Create(frame, []magicform.Field{
		{
			Label:       "Width:",
			Font:        balance.LabelFont,
			Type:        magicform.Selectbox,
			IntVariable: &doodadWidth,
			Options:     sizeOptions,
			OnSelect: func(v interface{}) {
				if v.(int) == 0 {
					shmem.Prompt("Enter a custom size for the doodad width: ", func(answer string) {
						if a, err := strconv.Atoi(answer); err == nil && a > 0 {
							doodadWidth = a
						} else {
							shmem.FlashError("Doodad size should be a number greater than zero.")
						}
					})
				}
			},
		},
		{
			Label:       "Height:",
			Font:        balance.LabelFont,
			Type:        magicform.Selectbox,
			IntVariable: &doodadHeight,
			Options:     sizeOptions,
			OnSelect: func(v interface{}) {
				if v.(int) == 0 {
					shmem.Prompt("Enter a custom size for the doodad height: ", func(answer string) {
						if a, err := strconv.Atoi(answer); err == nil && a > 0 {
							doodadHeight = a
						} else {
							shmem.FlashError("Doodad size should be a number greater than zero.")
						}
					})
				}
			},
		},
		{
			Buttons: []magicform.Field{
				{
					Label:       "Continue",
					Font:        balance.UIFont,
					ButtonStyle: &balance.ButtonPrimary,
					OnClick: func() {
						if config.OnCreateNewDoodad != nil {
							config.OnCreateNewDoodad(doodadWidth, doodadHeight)
						} else {
							shmem.FlashError("OnCreateNewDoodad not attached")
						}
					},
				},
				{
					Label:       "Cancel",
					Font:        balance.UIFont,
					ButtonStyle: &balance.ButtonPrimary,
					OnClick: func() {
						if config.OnCancel != nil {
							config.OnCancel()
						} else {
							shmem.FlashError("OnCancel not attached")
						}
					},
				},
			},
		},
	})

}

// Creates the Game Rules frame for existing level (set difficulty, etc.)
func (config AddEditLevel) setupGameRuleFrame(tf *ui.TabFrame) {
	frame := tf.AddTab("GameRules", ui.NewLabel(ui.Label{
		Text: "Game Rules",
		Font: balance.TabFont,
	}))

	form := magicform.Form{
		Supervisor: config.Supervisor,
		Engine:     config.Engine,
		Vertical:   true,
		LabelWidth: 120,
		PadY:       2,
	}
	fields := []magicform.Field{
		{
			Label: "Game Rules are specific to this level and can change some of\n" +
				"the game's default behaviors.",
			Font: balance.UIFont,
		},
		{
			Label:       "Difficulty:",
			Font:        balance.UIFont,
			SelectValue: config.EditLevel.GameRule.Difficulty,
			Tooltip: ui.Tooltip{
				Text: "Peaceful: enemies may not attack\n" +
					"Normal: default difficulty\n" +
					"Hard: enemies may be more aggressive",
				Edge: ui.Top,
			},
			Options: []magicform.Option{
				{
					Label: "Peaceful",
					Value: enum.Peaceful,
				},
				{
					Label: "Normal (recommended)",
					Value: enum.Normal,
				},
				{
					Label: "Hard",
					Value: enum.Hard,
				},
			},
			OnSelect: func(v interface{}) {
				value, _ := v.(enum.Difficulty)
				config.EditLevel.GameRule.Difficulty = value
				log.Info("Set level difficulty to: %d (%s)", value, value)
			},
		},
		{
			Label:        "Survival Mode (silver high score)",
			Font:         balance.UIFont,
			BoolVariable: &config.EditLevel.GameRule.Survival,
			Tooltip: ui.Tooltip{
				Text: "Use for levels where dying at least once is very likely\n" +
					"(e.g. Azulian Tag). The silver high score will be for\n" +
					"longest time rather than fastest time. The gold high\n" +
					"score will still be for fastest time.",
				Edge: ui.Top,
			},
		},
	}

	form.Create(frame, fields)
}

// Level Screenshot management frame.
func (config AddEditLevel) setupScreenshotFrame(tf *ui.TabFrame) {
	frame := tf.AddTab("Screenshot", ui.NewLabel(ui.Label{
		Text: "Screenshot",
		Font: balance.TabFont,
	}))

	var image *ui.Image

	// Have a screenshot already?
	if config.EditLevel.HasScreenshot() {
		if img, err := config.EditLevel.GetScreenshotImageAsUIImage(balance.LevelScreenshotSmallFilename); err != nil {
			lbl := ui.NewLabel(ui.Label{
				Text: err.Error(),
				Font: balance.DangerFont,
			})
			frame.Pack(lbl, ui.Pack{
				Side: ui.N,
			})
		} else {
			// Draw the image.
			log.Error("Got img: %+v", img)
			frame.Pack(img, ui.Pack{
				Side: ui.N,
			})

			// Hold onto it in case we need to refresh it.
			image = img
		}
	} else {
		lbl := ui.NewLabel(ui.Label{
			Text: "This level has no screenshot available. If this is\n" +
				"a new drawing, its first screenshot will be created\n" +
				"upon save; playtest or close and re-open the level\n" +
				"then and its screenshot should appear here and can\n" +
				"be refreshed.",
			Font: balance.DangerFont,
		})
		frame.Pack(lbl, ui.Pack{
			Side: ui.N,
		})

		// Exit now - don't add the Refresh button in case of nil pointer exception
		// while we are in this state.
		return
	}

	// Image refresh button.
	btn := ui.NewButton("Refresh", ui.NewLabel(ui.Label{
		Text: "Update Screenshot",
		Font: balance.UIFont,
	}))
	ui.NewTooltip(btn, ui.Tooltip{
		Text: "Update the screenshot from your current scroll point\nin the level editor.",
	})

	btn.Handle(ui.Click, func(ed ui.EventData) error {
		// Take a new screenshot.
		if config.OnUpdateScreenshot == nil {
			shmem.FlashError("OnUpdateScreenshot handler not configured!")
		} else {
			if err := config.OnUpdateScreenshot(); err != nil {
				modal.Alert(err.Error()).WithTitle("Error updating screenshot")
			} else {
				// Get the updated screenshot image from the level.
				if img, err := config.EditLevel.GetScreenshotImage(balance.LevelScreenshotSmallFilename); err != nil {
					shmem.FlashError("Couldn't reload screenshot from level: %s", err)
				} else {
					image.ReplaceFromImage(img)
				}
			}
		}
		return nil
	})

	btn.Compute(config.Engine)

	frame.Pack(btn, ui.Pack{
		Side:   ui.N,
		Expand: true,
	})

	config.Supervisor.Add(btn)
}

// Creates the Game Rules frame for existing level (set difficulty, etc.)
func (config AddEditLevel) setupAdvancedFrame(tf *ui.TabFrame) {
	frame := tf.AddTab("Advanced", ui.NewLabel(ui.Label{
		Text: "Advanced",
		Font: balance.TabFont,
	}))

	form := magicform.Form{
		Supervisor: config.Supervisor,
		Engine:     config.Engine,
		Vertical:   true,
		LabelWidth: 120,
		PadY:       2,
	}
	fields := []magicform.Field{
		{
			Label: "Level UUID Number",
			Font:  balance.LabelFont,
		},
		{
			Label: "Levels are assigned a unique identifier (UUID) for the purpose\n" +
				"of saving your high scores for them (in levels which are part of\n" +
				"level packs). Your level's UUID is shown below. Click it to\n" +
				"re-roll a new UUID number (e.g. in case you save a new copy\n" +
				"of a level that you want to be distinct from its original.)",
			Font: balance.UIFont,
		},
		{
			Label:        "Level UUID:",
			Font:         balance.UIFont,
			TextVariable: &config.EditLevel.UUID,
			Tooltip: ui.Tooltip{
				Text: "Click to re-roll a new UUID value.",
				Edge: ui.Top,
			},
			OnClick: func() {
				modal.Confirm(
					"Are you sure you want to re-roll a new UUID value?\n\n" +
						"Saving with a new UUID will mark this level as distinct\n" +
						"from the previous version - if you place both versions\n" +
						"in a levelpack together they will have separate high\n" +
						"score values.",
				).WithTitle("Re-roll UUID").Then(func() {
					config.EditLevel.UUID = uuid.New().String()
				})
			},
		},
	}

	form.Create(frame, fields)
}
