package d2player

import (
	"fmt"

	"github.com/OpenDiablo2/OpenDiablo2/d2core/d2records"

	"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
	"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
	"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
	"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
	"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
	"github.com/OpenDiablo2/OpenDiablo2/d2core/d2item/diablo2item"
	"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
)

const (
	frameInventoryTopLeft     = 4
	frameInventoryTopRight    = 5
	frameInventoryBottomLeft  = 6
	frameInventoryBottomRight = 7
)

const (
	invCloseButtonX, invCloseButtonY = 419, 449
	invGoldButtonX, invGoldButtonY   = 485, 455
	invGoldLabelX, invGoldLabelY     = 510, 455
)

// NewInventory creates an inventory instance and returns a pointer to it
func NewInventory(asset *d2asset.AssetManager,
	ui *d2ui.UIManager,
	l d2util.LogLevel,
	gold int,
	record *d2records.InventoryRecord) (*Inventory, error) {
	itemTooltip := ui.NewTooltip(d2resource.FontFormal11, d2resource.PaletteStatic, d2ui.TooltipXCenter, d2ui.TooltipYBottom)

	itemFactory, err := diablo2item.NewItemFactory(asset)
	if err != nil {
		return nil, fmt.Errorf("during creating new item factory: %s", err)
	}

	mgp := NewMoveGoldPanel(asset, ui, gold, l)

	inventory := &Inventory{
		asset:       asset,
		uiManager:   ui,
		item:        itemFactory,
		grid:        NewItemGrid(asset, ui, l, record),
		originX:     record.Panel.Left,
		itemTooltip: itemTooltip,
		// originY: record.Panel.Top,
		originY:       0, // expansion data has these all offset by +60 ...
		gold:          gold,
		moveGoldPanel: mgp,
	}

	inventory.moveGoldPanel.SetOnCloseCb(func() { inventory.onCloseGoldPanel() })

	inventory.Logger = d2util.NewLogger()
	inventory.Logger.SetLevel(l)
	inventory.Logger.SetPrefix(logPrefix)

	return inventory, nil
}

// Inventory represents the inventory
type Inventory struct {
	asset         *d2asset.AssetManager
	item          *diablo2item.ItemFactory
	uiManager     *d2ui.UIManager
	panel         *d2ui.Sprite
	goldLabel     *d2ui.Label
	grid          *ItemGrid
	itemTooltip   *d2ui.Tooltip
	panelGroup    *d2ui.WidgetGroup
	hoverX        int
	hoverY        int
	originX       int
	originY       int
	lastMouseX    int
	lastMouseY    int
	hovering      bool
	isOpen        bool
	onCloseCb     func()
	gold          int
	moveGoldPanel *MoveGoldPanel

	*d2util.Logger
}

// Toggle negates the open state of the inventory
func (g *Inventory) Toggle() {
	if g.isOpen {
		g.Close()
	} else {
		g.Open()
	}
}

// Load the resources required by the inventory
func (g *Inventory) Load() {
	var err error

	g.panelGroup = g.uiManager.NewWidgetGroup(d2ui.RenderPriorityInventory)

	frame := g.uiManager.NewUIFrame(d2ui.FrameRight)
	g.panelGroup.AddWidget(frame)

	g.panel, err = g.uiManager.NewSprite(d2resource.InventoryCharacterPanel, d2resource.PaletteSky)
	if err != nil {
		g.Error(err.Error())
	}

	closeButton := g.uiManager.NewButton(d2ui.ButtonTypeSquareClose, "")
	closeButton.SetVisible(false)
	closeButton.SetPosition(invCloseButtonX, invCloseButtonY)
	closeButton.OnActivated(func() { g.Close() })
	g.panelGroup.AddWidget(closeButton)

	goldButton := g.uiManager.NewButton(d2ui.ButtonTypeGoldCoin, "")
	goldButton.SetVisible(false)
	goldButton.SetPosition(invGoldButtonX, invGoldButtonY)
	goldButton.OnActivated(func() { g.onGoldClicked() })

	// nolint:gocritic // this variable will be used in future
	// deposite := g.asset.TranslateString("strGoldDeposit")
	drop := g.asset.TranslateString("strGoldDrop")
	// nolint:gocritic // this variable will be used in future
	// withdraw := g.asset.TranslateString("strGoldWithdraw")

	tooltip := g.uiManager.NewTooltip(d2resource.Font16, d2resource.PaletteSky, d2ui.TooltipXCenter, d2ui.TooltipYBottom)
	// here should be switch-case statement for each of move-gold button descr
	tooltip.SetText(drop)
	tooltip.SetPosition(invGoldButtonX, invGoldButtonY)
	goldButton.SetTooltip(tooltip)

	g.panelGroup.AddWidget(goldButton)

	g.goldLabel = g.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteStatic)
	g.goldLabel.Alignment = d2ui.HorizontalAlignLeft
	g.goldLabel.SetText(fmt.Sprintln(g.moveGoldPanel.gold))
	g.goldLabel.SetPosition(invGoldLabelX, invGoldLabelY)
	g.panelGroup.AddWidget(g.goldLabel)

	// https://github.com/OpenDiablo2/OpenDiablo2/issues/795
	testInventoryCodes := [][]string{
		{"kit", "Crimson", "of the Bat", "of Frost"},
		{"rin", "Steel", "of Shock"},
		{"jav"},
		{"buc"},
	}

	inventoryItems := make([]InventoryItem, 0)

	for idx := range testInventoryCodes {
		item, itemErr := g.item.NewItem(testInventoryCodes[idx]...)
		if itemErr != nil {
			continue
		}

		item.Identify()
		inventoryItems = append(inventoryItems, item)
	}

	// https://github.com/OpenDiablo2/OpenDiablo2/issues/795
	testEquippedItemCodes := map[d2enum.EquippedSlot][]string{
		d2enum.EquippedSlotLeftArm:   {"wnd"},
		d2enum.EquippedSlotRightArm:  {"buc"},
		d2enum.EquippedSlotHead:      {"crn"},
		d2enum.EquippedSlotTorso:     {"plt"},
		d2enum.EquippedSlotLegs:      {"vbt"},
		d2enum.EquippedSlotBelt:      {"vbl"},
		d2enum.EquippedSlotGloves:    {"lgl"},
		d2enum.EquippedSlotLeftHand:  {"rin"},
		d2enum.EquippedSlotRightHand: {"rin"},
		d2enum.EquippedSlotNeck:      {"amu"},
	}

	for slot := range testEquippedItemCodes {
		item, itemErr := g.item.NewItem(testEquippedItemCodes[slot]...)
		if itemErr != nil {
			continue
		}

		g.grid.ChangeEquippedSlot(slot, item)
	}

	_, err = g.grid.Add(inventoryItems...)
	if err != nil {
		g.Errorf("could not add items to the inventory, err: %v", err.Error())
	}

	g.moveGoldPanel.Load()

	g.panelGroup.SetVisible(false)
}

// Open opens the inventory
func (g *Inventory) Open() {
	g.isOpen = true
	g.panelGroup.SetVisible(true)
}

// Close closes the inventory
func (g *Inventory) Close() {
	g.isOpen = false
	g.moveGoldPanel.Close()
	g.panelGroup.SetVisible(false)
	g.itemTooltip.SetVisible(false)
	g.onCloseCb()
}

// SetOnCloseCb the callback run on closing the inventory
func (g *Inventory) SetOnCloseCb(cb func()) {
	g.onCloseCb = cb
}

func (g *Inventory) onGoldClicked() {
	g.Info("Move gold action clicked")
	g.toggleMoveGoldPanel()
}

func (g *Inventory) toggleMoveGoldPanel() {
	g.moveGoldPanel.Toggle()
}

func (g *Inventory) onCloseGoldPanel() {

}

// IsOpen returns true if the inventory is open
func (g *Inventory) IsOpen() bool {
	return g.isOpen
}

// Advance advances the state of the Inventory
func (g *Inventory) Advance(_ float64) {
	if !g.IsOpen() {
		return
	}

	g.goldLabel.SetText(fmt.Sprintln(g.moveGoldPanel.gold))
}

// Render draws the inventory onto the given surface
func (g *Inventory) Render(target d2interface.Surface) {
	if !g.isOpen {
		return
	}

	g.renderFrame(target)

	g.grid.Render(target)
	g.showItemDescriptionTooltip()
}

func (g *Inventory) renderFrame(target d2interface.Surface) {
	frames := []int{
		frameInventoryTopLeft,
		frameInventoryTopRight,
		frameInventoryBottomRight,
		frameInventoryBottomLeft,
	}

	x, y := g.originX+1, g.originY
	y += 64

	for _, frame := range frames {
		if err := g.panel.SetCurrentFrame(frame); err != nil {
			g.Error(err.Error())
			return
		}

		w, h := g.panel.GetCurrentFrameSize()

		g.panel.SetPosition(x, y+h)
		g.panel.Render(target)

		switch frame {
		case frameInventoryTopLeft:
			x += w
		case frameInventoryTopRight:
			y += h
		case frameInventoryBottomRight:
			x = g.originX + 1
		}
	}
}

func (g *Inventory) showItemDescriptionTooltip() {
	hoveringNow := g.checkEquippedSlotsHover()

	if hoveringNow {
		g.hovering = true
		return
	}

	hoveringNow = g.checkGridSlotsHover()

	if !hoveringNow {
		g.itemTooltip.SetVisible(false)
	}

	g.hovering = hoveringNow
}

func (g *Inventory) checkGridSlotsHover() bool {
	hovering := false

	for idx := range g.grid.items {
		item := g.grid.items[idx]
		ix, iy := g.grid.SlotToScreen(item.InventoryGridSlot())
		iw, ih := g.grid.sprites[item.GetItemCode()].GetCurrentFrameSize()
		mx, my := g.lastMouseX, g.lastMouseY
		hovering = hovering || ((mx > ix) && (mx < ix+iw) && (my > iy) && (my < iy+ih))

		if hovering {
			if !g.hovering {
				// set the initial hover coordinates
				// this is so that moving mouse doesnt move the description
				g.hoverX, g.hoverY = mx, my
			}

			g.showGridItemDescriptionTooltip(item)

			break
		}
	}

	return hovering
}

func (g *Inventory) checkEquippedSlotsHover() bool {
	hovering := false

	for _, slot := range g.grid.equipmentSlots {
		mx, my := g.lastMouseX, g.lastMouseY

		hovering = hovering || ((mx > slot.x) && (mx < slot.x+slot.width) && (my < slot.y) && (my > slot.y-slot.height))
		if hovering {
			if !g.hovering {
				g.hoverX, g.hoverY = mx, my
			}

			g.showEquippedItemDescriptionTooltip(slot)

			break
		}
	}

	return hovering
}

func (g *Inventory) showGridItemDescriptionTooltip(i InventoryItem) {
	if !g.moveGoldPanel.IsOpen() {
		lines := i.GetItemDescription()
		g.itemTooltip.SetTextLines(lines)
		_, y := g.grid.SlotToScreen(i.InventoryGridSlot())

		g.itemTooltip.SetPosition(g.hoverX, y)
		g.itemTooltip.SetVisible(true)
	}
}

func (g *Inventory) showEquippedItemDescriptionTooltip(slot EquipmentSlot) {
	if !g.moveGoldPanel.IsOpen() {
		lines := slot.item.GetItemDescription()
		g.itemTooltip.SetTextLines(lines)
		g.itemTooltip.SetPosition(g.hoverX, slot.y)
		g.itemTooltip.SetVisible(true)
	}
}
