package d2player

import (
	"log"
	"strconv"

	"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
	"github.com/OpenDiablo2/OpenDiablo2/d2common/d2geom"
	"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/d2hero"
	"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity"
	"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
)

const (
	lightGreen = 0x18ff00ff
	red        = 0xff0000ff
	lightRed   = 0xdb3f3dff
	orange     = 0xffa800ff
)

const ( // for the dc6 frames
	partyPanelTopLeft = iota
	partyPanelTopRight
	partyPanelBottomLeft
	partyPanelBottomRight
)

const ( // for bar's dc6 frames
	barLeft = iota
	barRight
)

const (
	partyPanelOffsetX, partyPanelOffsetY = 80, 64
)

const (
	partyPanelCloseButtonX, partyPanelCloseButtonY = 358, 453
	partyPanelHeroNameX, partyPanelHeroNameY       = 180, 80
)

const (
	buttonSize = 19
)

const (
	barX, baseBarY                                   = 90, 134
	relationshipSwitcherX, baseRelationshipSwitcherY = 95, 150
	listeningSwitcherX, baseListeningSwitcherY       = 342, 140
	seeingSwitcherX, baseSeeingSwitcherY             = 365, 140
	nameLabelX, baseNameLabelY                       = 115, 144
	nameTooltipX, baseNameTooltipY                   = 100, 120
	classLabelX, baseClassLabelY                     = 115, 158
	levelLabelX, baseLevelLabelY                     = 386, 160
	inviteAcceptButtonX, baseInviteAcceptButtonY     = 265, 147
	indexOffset                                      = 52
)

// NewPartyPanel creates a new party panel
func NewPartyPanel(asset *d2asset.AssetManager,
	ui *d2ui.UIManager,
	heroName string,
	l d2util.LogLevel,
	me *d2mapentity.Player,
	heroState *d2hero.HeroStatsState,
	players map[string]*d2mapentity.Player) *PartyPanel {
	log.Print("OpenDiablo2 - Party Panel - development")

	originX := 0
	originY := 0

	pp := &PartyPanel{
		asset:     asset,
		uiManager: ui,
		originX:   originX,
		originY:   originY,
		heroState: heroState,
		heroName:  heroName,
		labels:    &StatsPanelLabels{},
		barX:      barX,
		barY:      baseBarY,
		players:   players,
		me:        me,
	}

	var partyIndexes [d2enum.MaxPlayersInGame]*partyIndex

	var indexes [d2enum.MaxPlayersInGame]*d2ui.WidgetGroup

	for i := 0; i < d2enum.MaxPlayersInGame; i++ {
		partyIndexes[i] = pp.newPartyIndex()
		indexes[i] = pp.uiManager.NewWidgetGroup(d2ui.RenderPriorityHeroStatsPanel)
	}

	pp.partyIndexes = partyIndexes

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

	return pp
}

// PartyPanel represents the party panel
type PartyPanel struct {
	asset      *d2asset.AssetManager
	uiManager  *d2ui.UIManager
	panel      *d2ui.Sprite
	bar        *d2ui.Sprite
	heroState  *d2hero.HeroStatsState
	heroName   string
	labels     *StatsPanelLabels
	onCloseCb  func()
	panelGroup *d2ui.WidgetGroup

	partyIndexes [d2enum.MaxPlayersInGame]*partyIndex
	indexes      [d2enum.MaxPlayersInGame]*d2ui.WidgetGroup

	players map[string]*d2mapentity.Player
	me      *d2mapentity.Player

	originX int
	originY int
	isOpen  bool
	barX    int
	barY    int

	*d2util.Logger
}

// newPartyIndex creates new party index
func (s *PartyPanel) newPartyIndex() *partyIndex {
	result := &partyIndex{
		asset: s.asset,
		me:    s.me,
	}

	nameLabel := s.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteSky)
	result.nameTooltip = s.uiManager.NewTooltip(d2resource.Font16, d2resource.PaletteSky, d2ui.TooltipXCenter, d2ui.TooltipYTop)
	result.name = nameLabel

	classLabel := s.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteSky)
	result.class = classLabel

	result.nameRect = d2geom.Rectangle{}

	levelLabel := s.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteSky)
	levelLabel.Alignment = d2ui.HorizontalAlignRight
	result.level = levelLabel

	relationships := s.createSwitcher(d2enum.PartyButtonRelationshipsFrame)
	relationships.SetDisabledColor(lightRed)

	result.relationshipsActiveTooltip = s.uiManager.NewTooltip(d2resource.Font16, d2resource.PaletteSky, d2ui.TooltipXCenter, d2ui.TooltipYTop)
	result.relationshipsActiveTooltip.SetText(s.asset.TranslateString("strParty7") + "\n" + s.asset.TranslateString("strParty8"))
	relationships.SetActiveTooltip(result.relationshipsActiveTooltip)

	result.relationshipsInactiveTooltip = s.uiManager.NewTooltip(d2resource.Font16, d2resource.PaletteSky,
		d2ui.TooltipXCenter, d2ui.TooltipYTop)
	result.relationshipsInactiveTooltip.SetText(s.asset.TranslateString("strParty9") + "\n" + s.asset.TranslateString("strParty8"))
	relationships.SetInactiveTooltip(result.relationshipsInactiveTooltip)

	result.relationshipSwitcher = relationships

	seeing := s.createSwitcher(d2enum.PartyButtonSeeingFrame)

	result.seeingActiveTooltip = s.uiManager.NewTooltip(d2resource.Font16, d2resource.PaletteSky, d2ui.TooltipXCenter, d2ui.TooltipYTop)
	result.seeingActiveTooltip.SetText(s.asset.TranslateString("strParty19"))
	seeing.SetActiveTooltip(result.seeingActiveTooltip)

	result.seeingInactiveTooltip = s.uiManager.NewTooltip(d2resource.Font16, d2resource.PaletteSky, d2ui.TooltipXCenter, d2ui.TooltipYTop)
	result.seeingInactiveTooltip.SetText(s.asset.TranslateString("strParty22"))
	seeing.SetInactiveTooltip(result.seeingInactiveTooltip)

	result.seeingSwitcher = seeing

	listening := s.createSwitcher(d2enum.PartyButtonListeningFrame)

	result.listeningActiveTooltip = s.uiManager.NewTooltip(d2resource.Font16, d2resource.PaletteSky, d2ui.TooltipXCenter, d2ui.TooltipYTop)
	result.listeningActiveTooltip.SetText(s.asset.TranslateString("strParty17") + "\n" + s.asset.TranslateString("strParty18"))
	listening.SetActiveTooltip(result.listeningActiveTooltip)

	result.listeningInactiveTooltip = s.uiManager.NewTooltip(d2resource.Font16, d2resource.PaletteSky, d2ui.TooltipXCenter, d2ui.TooltipYTop)
	result.listeningInactiveTooltip.SetText(s.asset.TranslateString("strParty11") + "\n" + s.asset.TranslateString("strParty16"))
	listening.SetInactiveTooltip(result.listeningInactiveTooltip)

	result.listeningSwitcher = listening

	result.inviteAcceptButton = s.uiManager.NewButton(d2ui.ButtonTypePartyButton, s.asset.TranslateString("Invite"))
	result.inviteAcceptButton.SetVisible(false)

	return result
}

// partyIndex represents a party index
type partyIndex struct {
	asset *d2asset.AssetManager
	me    *d2mapentity.Player

	hero                         *d2mapentity.Player
	name                         *d2ui.Label
	nameTooltip                  *d2ui.Tooltip
	nameRect                     d2geom.Rectangle
	class                        *d2ui.Label
	level                        *d2ui.Label
	relationshipSwitcher         *d2ui.SwitchableButton
	relationshipsActiveTooltip   *d2ui.Tooltip
	relationshipsInactiveTooltip *d2ui.Tooltip
	seeingSwitcher               *d2ui.SwitchableButton
	seeingActiveTooltip          *d2ui.Tooltip
	seeingInactiveTooltip        *d2ui.Tooltip
	listeningSwitcher            *d2ui.SwitchableButton
	listeningActiveTooltip       *d2ui.Tooltip
	listeningInactiveTooltip     *d2ui.Tooltip
	inviteAcceptButton           *d2ui.Button
	relationships                d2enum.PlayersRelationships
}

func (pi *partyIndex) setNameTooltipText() {
	switch pi.relationships {
	case d2enum.PlayerRelationNeutral, d2enum.PlayerRelationFriend:
		pi.nameTooltip.SetText(pi.asset.TranslateString("Party17"))
	case d2enum.PlayerRelationEnemy:
		pi.nameTooltip.SetText(pi.asset.TranslateString("Party12"))
	}
}

// setColor sets appropriate labels' colors
func (pi *partyIndex) setColor(relations d2enum.PlayersRelationships) {
	var color = d2util.Color(white)

	switch relations {
	case d2enum.PlayerRelationEnemy:
		color = d2util.Color(red)

		pi.relationshipSwitcher.SetState(false)
	case d2enum.PlayerRelationFriend:
		color = d2util.Color(lightGreen)
	case d2enum.PlayerRelationNeutral:
		if pi.CanGoHostile() {
			color = d2util.Color(white)
		} else {
			color = d2util.Color(orange)
			pi.relationshipSwitcher.SetEnabled(false)
		}
	}

	pi.name.Color[0] = color
	pi.class.Color[0] = color
	pi.level.Color[0] = color
}

// setPositions sets party-index's position to given
func (pi *partyIndex) setPositions(idx int) {
	var w, h int

	pi.name.SetPosition(nameLabelX, baseNameLabelY+indexOffset*idx)
	pi.nameTooltip.SetPosition(nameTooltipX, baseNameTooltipY+indexOffset*idx)
	pi.class.SetPosition(classLabelX, baseClassLabelY+indexOffset*idx)
	pi.level.SetPosition(levelLabelX, baseLevelLabelY+indexOffset*idx)

	w, h1 := pi.class.GetSize()

	_, h = pi.name.GetSize()

	pi.nameRect = d2geom.Rectangle{
		Left:   nameLabelX,
		Top:    baseNameLabelY + idx*indexOffset,
		Width:  w,
		Height: h + h1,
	}

	pi.relationshipSwitcher.SetPosition(relationshipSwitcherX, baseRelationshipSwitcherY+indexOffset*idx)
	_, h = pi.relationshipsActiveTooltip.GetSize()
	pi.relationshipsActiveTooltip.SetPosition(relationshipSwitcherX+buttonSize, baseRelationshipSwitcherY+idx*indexOffset-h)
	_, h = pi.relationshipsInactiveTooltip.GetSize()
	pi.relationshipsInactiveTooltip.SetPosition(relationshipSwitcherX+buttonSize, baseRelationshipSwitcherY+idx*indexOffset-h)

	pi.seeingSwitcher.SetPosition(seeingSwitcherX, baseSeeingSwitcherY+idx*indexOffset)
	_, h = pi.seeingActiveTooltip.GetSize()
	pi.seeingActiveTooltip.SetPosition(seeingSwitcherX+buttonSize, baseSeeingSwitcherY+idx*indexOffset-h)
	_, h = pi.seeingInactiveTooltip.GetSize()
	pi.seeingInactiveTooltip.SetPosition(seeingSwitcherX+buttonSize, baseSeeingSwitcherY+idx*indexOffset-h)

	pi.listeningSwitcher.SetPosition(listeningSwitcherX, baseListeningSwitcherY+idx*indexOffset)
	_, h = pi.listeningActiveTooltip.GetSize()
	pi.listeningActiveTooltip.SetPosition(listeningSwitcherX+buttonSize, baseListeningSwitcherY+idx*indexOffset-h)
	_, h = pi.listeningInactiveTooltip.GetSize()
	pi.listeningInactiveTooltip.SetPosition(listeningSwitcherX+buttonSize, baseListeningSwitcherY+idx*indexOffset-h)

	pi.inviteAcceptButton.SetPosition(inviteAcceptButtonX, baseInviteAcceptButtonY+idx*indexOffset)
}

func (pi *partyIndex) CanGoHostile() bool {
	return pi.hero.Stats.Level >= d2enum.PlayersHostileLevel && pi.me.Stats.Level >= d2enum.PlayersHostileLevel
}

// Load the data for the hero status panel
func (s *PartyPanel) Load() {
	var err error

	var w, h int

	// create widgetGroups
	s.panelGroup = s.uiManager.NewWidgetGroup(d2ui.RenderPriorityHeroStatsPanel)
	for i := 0; i < d2enum.MaxPlayersInGame; i++ {
		s.indexes[i] = s.uiManager.NewWidgetGroup(d2ui.RenderPriorityHeroStatsPanel)
	}

	// create frame
	frame := s.uiManager.NewUIFrame(d2ui.FrameLeft)
	s.panelGroup.AddWidget(frame)

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

	// create panel
	w, h = frame.GetSize()
	staticPanel := s.uiManager.NewCustomWidgetCached(s.renderStaticPanelFrames, w, h)
	s.panelGroup.AddWidget(staticPanel)

	// create close button
	closeButton := s.uiManager.NewButton(d2ui.ButtonTypeSquareClose, "")
	closeButton.SetVisible(false)
	closeButton.SetPosition(partyPanelCloseButtonX, partyPanelCloseButtonY)
	closeButton.OnActivated(func() { s.Close() })
	s.panelGroup.AddWidget(closeButton)

	// our name label
	heroName := s.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteSky)
	heroName.SetText(s.heroName)
	heroName.SetPosition(partyPanelHeroNameX, partyPanelHeroNameY)
	heroName.Alignment = d2ui.HorizontalAlignCenter
	s.panelGroup.AddWidget(heroName)

	// create WidgetGroups of party indexes
	for n, i := range s.partyIndexes {
		s.indexes[n].AddWidget(i.name)
		s.indexes[n].AddWidget(i.class)
		s.indexes[n].AddWidget(i.relationshipSwitcher)
		s.indexes[n].AddWidget(i.seeingSwitcher)
		s.indexes[n].AddWidget(i.listeningSwitcher)
		s.indexes[n].AddWidget(i.level)
		s.indexes[n].AddWidget(i.inviteAcceptButton)
	}

	// create bar
	s.bar, err = s.uiManager.NewSprite(d2resource.PartyBar, d2resource.PaletteSky)
	if err != nil {
		s.Error(err.Error())
	}

	w, h = s.bar.GetCurrentFrameSize()
	v := s.uiManager.NewCustomWidget(s.renderBar, w, h)
	s.panelGroup.AddWidget(v)

	s.setBarPosition()

	s.panelGroup.SetVisible(false)
}

// createSwitcher creates party-panel switcher using frame given
func (s *PartyPanel) createSwitcher(frame int) *d2ui.SwitchableButton {
	active := s.uiManager.NewDefaultButton(d2resource.PartyBoxes, frame)
	inactive := s.uiManager.NewDefaultButton(d2resource.PartyBoxes, frame+d2enum.PartyButtonNextButtonFrame)
	switcher := s.uiManager.NewSwitchableButton(active, inactive, true)
	switcher.SetVisible(false)

	return switcher
}

// IsOpen returns true if the hero status panel is open
func (s *PartyPanel) IsOpen() bool {
	return s.isOpen
}

// Toggle toggles the visibility of the hero status panel
func (s *PartyPanel) Toggle() {
	if s.isOpen {
		s.Close()
	} else {
		s.Open()
	}
}

// Open opens the hero status panel
func (s *PartyPanel) Open() {
	s.isOpen = true
	s.panelGroup.SetVisible(true)

	for n, i := range s.indexes {
		if s.partyIndexes[n].hero != nil {
			i.SetVisible(true)
		}
	}
}

// Close closed the hero status panel
func (s *PartyPanel) Close() {
	s.isOpen = false
	s.panelGroup.SetVisible(false)

	for _, i := range s.indexes {
		i.SetVisible(false)
	}
}

// SetOnCloseCb the callback run on closing the PartyPanel
func (s *PartyPanel) SetOnCloseCb(cb func()) {
	s.onCloseCb = cb
}

// AddPlayer adds a new player to the party panel
func (s *PartyPanel) AddPlayer(player *d2mapentity.Player, relations d2enum.PlayersRelationships) {
	idx := 0

	// search for free index
	for n, i := range s.partyIndexes {
		if i.hero == nil {
			idx = n
			break
		}
	}

	s.partyIndexes[idx].hero = player

	s.partyIndexes[idx].name.SetText(player.Name())

	s.partyIndexes[idx].class.SetText(s.asset.TranslateString(player.Class.String()))

	s.partyIndexes[idx].level.SetText(s.asset.TranslateString("Level") + ":" + strconv.Itoa(player.Stats.Level))

	s.partyIndexes[idx].relationships = relations

	s.partyIndexes[idx].setColor(relations)

	s.partyIndexes[idx].setPositions(idx)

	s.partyIndexes[idx].setNameTooltipText()
}

// DeletePlayer deletes player from PartyIndexes
func (s *PartyPanel) DeletePlayer(player *d2mapentity.Player) bool {
	for n, i := range s.partyIndexes {
		if i.hero == player {
			s.Debugf("removing player at index %d", n)

			s.partyIndexes[n].hero = nil
			s.Sort()

			return true
		}
	}

	return false
}

// Sort sorts party indexes
func (s *PartyPanel) Sort() {
	var emptySlots []*partyIndex

	var emptySlotsNumbers []int

	var fullSlots []*partyIndex

	var fullSlotsNumbers []int

	// split s.partyIndexes to empty and non-empty
	for n, i := range s.partyIndexes {
		if i.hero == nil {
			emptySlots = append(emptySlots, i)
			emptySlotsNumbers = append(emptySlotsNumbers, n)
		} else {
			fullSlots = append(fullSlots, i)
			fullSlotsNumbers = append(fullSlotsNumbers, n)
		}
	}

	// adds non-empty indexes befor empty indexes
	for n, i := range fullSlots {
		s.partyIndexes[n] = i
	}

	// adds empty indexes
	for n, i := range emptySlots {
		s.partyIndexes[len(fullSlots)+n] = i
	}

	// sorts widget groups
	var sortedWG [d2enum.MaxPlayersInGame]*d2ui.WidgetGroup
	// first add non empty WG's
	for n, i := range fullSlotsNumbers {
		sortedWG[n] = s.indexes[i]
	}

	// after that, adds empty WG's
	for n, i := range emptySlotsNumbers {
		sortedWG[len(fullSlotsNumbers)+n] = s.indexes[i]
	}

	// overwrite existing order
	s.indexes = sortedWG

	// sets appropriate positions
	for n, i := range s.partyIndexes {
		if i.hero != nil {
			i.setPositions(n)
		}
	}
}

// IsInPanel returns true if player given already exists in panel
func (s *PartyPanel) IsInPanel(player *d2mapentity.Player) bool {
	for _, i := range s.partyIndexes {
		if i.hero == player {
			return true
		}
	}

	return false
}

// IsMe returns true if player given is "me"
func (s *PartyPanel) IsMe(player *d2mapentity.Player) bool {
	return player == s.me
}

// setBarPosition sets party-panel bar's position
func (s *PartyPanel) setBarPosition() {
	for n, i := range s.partyIndexes {
		currentN := n

		if i.hero == nil {
			s.barX, s.barY = barX, baseBarY+currentN*indexOffset
			break
		}
	}
}

// UpdatePanel updates panel indexes with players list
func (s *PartyPanel) UpdatePanel() {
	for _, i := range s.players {
		if !s.IsInPanel(i) && !s.IsMe(i) {
			s.AddPlayer(i, d2enum.PlayerRelationNeutral)

			// we need to switch all hidden widgets to be visible
			// s.Open contains appropriate code to do that.
			if s.IsOpen() {
				s.Open()
			}
		}
	}
}

// UpdatePlayersList updates internal players list
func (s *PartyPanel) UpdatePlayersList(list map[string]*d2mapentity.Player) {
	s.players = list
}

// Advance advances panel
func (s *PartyPanel) Advance(_ float64) {
	if !s.IsOpen() {
		return
	}

	s.UpdatePanel()
}

// OnMouseMove handles mouse movement events
func (s *PartyPanel) OnMouseMove(event d2interface.MouseMoveEvent) bool {
	mx, my := event.X(), event.Y()

	for _, i := range s.partyIndexes {
		// Mouse over a game control element
		if i.nameRect.IsInRect(mx, my) {
			i.nameTooltip.SetVisible(true)
		} else {
			i.nameTooltip.SetVisible(false)
		}
	}

	return true
}

// nolint:dupl // see quest_log.go.renderStaticPanelFrames comment
func (s *PartyPanel) renderStaticPanelFrames(target d2interface.Surface) {
	frames := []int{
		partyPanelTopLeft,
		partyPanelTopRight,
		partyPanelBottomRight,
		partyPanelBottomLeft,
	}

	currentX := s.originX + partyPanelOffsetX
	currentY := s.originY + partyPanelOffsetY

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

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

		switch frameIndex {
		case statsPanelTopLeft:
			s.panel.SetPosition(currentX, currentY+h)
			currentX += w
		case statsPanelTopRight:
			s.panel.SetPosition(currentX, currentY+h)
			currentY += h
		case statsPanelBottomRight:
			s.panel.SetPosition(currentX, currentY+h)
		case statsPanelBottomLeft:
			s.panel.SetPosition(currentX-w, currentY+h)
		}

		s.panel.Render(target)
	}
}

// renderBar renders party panel's bar
func (s *PartyPanel) renderBar(target d2interface.Surface) {
	frames := []int{
		barLeft,
		barRight,
	}

	currentX := s.originX + s.barX
	currentY := s.originY + s.barY

	for _, frameIndex := range frames {
		if err := s.bar.SetCurrentFrame(frameIndex); err != nil {
			s.Error(err.Error())
		}

		w, h := s.bar.GetCurrentFrameSize()

		switch frameIndex {
		case statsPanelTopLeft:
			s.bar.SetPosition(currentX, currentY)
			currentX += w
		case statsPanelTopRight:
			s.bar.SetPosition(currentX, currentY)
			currentY += h
		}

		s.bar.Render(target)
	}
}
