# GemRB - Infinity Engine Emulator
# Copyright (C) 2003-2004 The GemRB Project
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# LUProfsSelection.py - Modular selection of proficiencies

import GemRB
from GUIDefines import *
from ie_stats import *
import GameCheck
import GUICommon
import CommonTables
from CommonWindow import AddScrollbarProxy

#the different types possible
LUPROFS_TYPE_LEVELUP = 1
LUPROFS_TYPE_DUALCLASS = 2
LUPROFS_TYPE_CHARGEN = 4

#refs to the script calling this
ProfsWindow = 0
ProfsCallback = 0

#all our offsets for the various parts of the window
ProfsOffsetSum = 0
ProfsOffsetButton1 = 0
ProfsOffsetLabel = 0
ProfsOffsetStar = 0
ProfsOffsetPress = 0
Profs2ndOffsetButton1 = Profs2ndOffsetStar = Profs2ndOffsetLabel = -1
ClassNameSave = 0
OddIDs = 0

#internal variables
ProfsPointsLeft = 0
ProfsNumButtons = 0
ProfsTextArea = 0
ProfsColumn = 0
ProfsTable = 0
ProfCount = 0

# the table offset is used in bg2, since in the end they made it use different
# profs than in bg1, but left the table entries intact
ProfsTableOffset = 0

# iwd1/bg1 table that holds the allowed proficiency <-> class mapping
ClassWeaponsTable = None

ProfsType = None

def SetupProfsWindow (pc, proftype, window, callback, level1=[0,0,0], level2=[1,1,1], classid=0, scroll=True, profTableOffset=8):
	"""Opens the proficiency selection window.

	type specifies the type of selection we are doing; choices are above.
	window specifies the window to be updated.
	callback specifies the function to call on changes.
	classid is sent only during dualclassing to specify the new class."""

	global ProfsOffsetSum, ProfsOffsetButton1, ProfsOffsetLabel, ProfsOffsetStar, OddIDs
	global ProfsOffsetPress, ProfsPointsLeft, ProfsNumButtons
	global ProfsTableOffset, ProfsType
	global ProfsWindow, ProfsCallback, ProfsTextArea, ProfsColumn, ProfsTable, ProfCount
	global Profs2ndOffsetButton1, Profs2ndOffsetStar, Profs2ndOffsetLabel, ClassNameSave, ClassWeaponsTable

	# make sure we're within ranges
	GemRB.SetVar ("ProfsPointsLeft", 0)
	if not window or not callback or len(level1)!=len(level2):
		return

	# save the values we'll point to
	ProfsWindow = window
	ProfsCallback = callback
	ProfsTableOffset = profTableOffset
	ProfsType = proftype
	ProfsScrollBar = None

	if proftype == LUPROFS_TYPE_CHARGEN and (GameCheck.IsBG1() or GameCheck.IsBG2OrEE ()):
		ProfsOffsetSum = 9
		ProfsOffsetButton1 = 11
		ProfsOffsetStar = 27
		ProfsOffsetLabel = 1
		ProfsOffsetPress = 69
		ProfsNumButtons = 8
		ProfsTextArea = ProfsWindow.GetControl (68)
		ProfsTextArea.SetText (9588)
		if (scroll):
			ProfsScrollBar = ProfsWindow.GetControl (78)

		if GameCheck.IsBG2OrEE () or GameCheck.IsIWD2 ():
			import CharGenCommon
			CharGenCommon.PositionCharGenWin (ProfsWindow)
	elif proftype == LUPROFS_TYPE_LEVELUP and GameCheck.IsBG2OrEE ():
		ProfsOffsetSum = 36
		ProfsOffsetButton1 = 1
		ProfsOffsetStar = 48
		ProfsOffsetLabel = 24
		ProfsOffsetPress = 112
		ProfsNumButtons = 7
		ProfsTextArea = ProfsWindow.GetControl (110)
		ProfsScrollBar = ProfsWindow.GetControl (108)
	elif proftype == LUPROFS_TYPE_LEVELUP and GameCheck.IsBG1(): #levelup
		ProfsOffsetSum = 36
		ProfsOffsetButton1 = 1
		ProfsOffsetStar = 48
		ProfsOffsetLabel = 24
		ProfsOffsetPress = 17
		ProfsNumButtons = 8
		ProfsTextArea = ProfsWindow.GetControl (42)
		if (scroll):
			ProfsScrollBar = ProfsWindow.GetControl (108)
	elif proftype == LUPROFS_TYPE_LEVELUP and GameCheck.IsIWD1(): #levelup
		ProfsOffsetSum = 36
		ProfsOffsetButton1 = 1
		ProfsOffsetStar = 48
		ProfsOffsetLabel = 24
		ProfsOffsetPress = -1
		ProfsNumButtons = 15 # 8+7, the 7 are done with the following vars
		Profs2ndOffsetButton1 = 150
		Profs2ndOffsetStar = 115
		Profs2ndOffsetLabel = 108
		ProfsTextArea = ProfsWindow.GetControl (42)
		if (scroll):
			ProfsScrollBar = ProfsWindow.GetControl (108)
		OddIDs = 0
	elif proftype == LUPROFS_TYPE_DUALCLASS and GameCheck.IsIWD1(): #dualclass
		ProfsOffsetSum = 40
		ProfsOffsetButton1 = 50
		ProfsOffsetStar = 0
		ProfsOffsetLabel = 41
		ProfsOffsetPress = -1 #66
		ProfsNumButtons = 15
		Profs2ndOffsetButton1 = 78
		Profs2ndOffsetStar = 92
		Profs2ndOffsetLabel = 126
		ProfsTextArea = ProfsWindow.GetControl (74)
		ProfsTextArea.SetText (9588)
		if (scroll):
			ProfsScrollBar = ProfsWindow.GetControl (None)
		OddIDs = 1
	elif proftype == LUPROFS_TYPE_DUALCLASS and GameCheck.IsBG1(): #dualclass
		ProfsOffsetSum = 40
		ProfsOffsetButton1 = 50
		ProfsOffsetStar = 0
		ProfsOffsetLabel = 41
		ProfsOffsetPress = -1 #FIXME
		ProfsNumButtons = 8
		ProfsTextArea = ProfsWindow.GetControl (74)
		ProfsTextArea.SetText (9588)
		if (scroll):
			ProfsScrollBar = ProfsWindow.GetControl (None)
	elif proftype == LUPROFS_TYPE_DUALCLASS: #dualclass
		ProfsOffsetSum = 40
		ProfsOffsetButton1 = 50
		ProfsOffsetStar = 0
		ProfsOffsetLabel = 41
		ProfsOffsetPress = 66
		ProfsNumButtons = 8
		ProfsTextArea = ProfsWindow.GetControl (74)
		ProfsTextArea.SetText (9588)
		if (scroll):
			ProfsScrollBar = ProfsWindow.GetControl (78)
	else: #unknown
		return

	#nullify internal variables
	GemRB.SetVar ("ProfsTopIndex", 0)
	ProfsPointsLeft = 0

	ProfsTable = GemRB.LoadTable ("profs")
	if GameCheck.IsIWD1() or GameCheck.IsBG1():
		ClassWeaponsTable = GemRB.LoadTable ("clasweap")
	else:
		ClassWeaponsTable = None

	#get the class name
	IsDual = GUICommon.IsDualClassed (pc, 1)
	if classid: #for dual classes when we can't get the class dualing to
		Class = classid
	elif IsDual[0] == 3:
		Class = CommonTables.KitList.GetValue (IsDual[2], 7, GTV_INT)
	elif IsDual[0]:
		Class = GUICommon.GetClassRowName(IsDual[2], "index")
		Class = CommonTables.Classes.GetValue (Class, "ID", GTV_INT)
	else:
		Class = GemRB.GetPlayerStat (pc, IE_CLASS)
	ClassName = GUICommon.GetClassRowName (Class, "class")

	# profs.2da has entries for everyone, so no need to muck around
	ProfsRate = ProfsTable.GetValue (ClassName, "RATE", GTV_INT)

	#figure out how many prof points we have
	if sum (level1) == 0: #character is being generated (either chargen or dual)
		ProfsPointsLeft = ProfsTable.GetValue (ClassName, "FIRST_LEVEL", GTV_INT)

	ProfIndex = 0
	IsMulti = GUICommon.IsMultiClassed (pc, 1)
	if IsMulti[0] > 1:
		if sum (level1) == 0: #character is being generated (either chargen or dual)
			# don't give too many points; exact formula unknown, but this works with f/m and f/m/t
			ProfsPointsLeft += sum(level2) // IsMulti[0] // ProfsRate
		else:
			# just look at the fastest prof-gaining class in the bunch and consider their level
			# eg. a m/t should get a point at 3/4 and 7/8 (rate of 4)
			BestRate = 100
			for cls in IsMulti[1:]:
				if cls == 0:
					break
				ClsName = GUICommon.GetClassRowName (cls, "class")
				Rate = ProfsTable.GetValue (ClsName, "RATE", GTV_INT)
				if Rate < BestRate:
					BestRate = Rate
					ProfIndex = IsMulti[1:].index(cls)

			ProfsPointsLeft += level2[ProfIndex] // ProfsRate - level1[ProfIndex] // ProfsRate
	else:
		if GUICommon.IsDualSwap (pc):
			ProfIndex = 1

		#we need these 2 number to floor before subtracting
		ProfsPointsLeft += level2[ProfIndex] // ProfsRate - level1[ProfIndex] // ProfsRate

	#setup prof vars for passing between functions
	ProfsTable = GemRB.LoadTable ("weapprof")

	# weapprof has no sorcerer entry
	if ClassName == "SORCERER":
		ClassName = "MAGE"
	ClassNameSave = ClassName

	# if we have the classweapons table, use it
	if ClassWeaponsTable:
		ProfsColumn = ClassWeaponsTable.GetRowIndex (ClassName)
	else:
		Kit = GUICommon.GetKitIndex (pc)
		if Kit and proftype != LUPROFS_TYPE_DUALCLASS and IsMulti[0]<2 and IsDual[0] in [0, 3]:
			#if we do kit with dualclass, we'll get the old kit (usually)
			#also don't want to worry about kitted multis
			ProfsColumn = CommonTables.KitList.GetValue (Kit, 5)
		else:
			ProfsColumn = ProfsTable.GetColumnIndex (ClassName)

	#setup some basic counts
	RowCount = ProfsTable.GetRowCount () - ProfsTableOffset
	ProfCount = RowCount-ProfsNumButtons #decrease it with the number of controls

	ProfsAssignable = 0
	TwoWeapIndex = ProfsTable.GetRowIndex ("2WEAPON")
	for i in range(RowCount):
		ProfName = ProfsTable.GetValue (i+ProfsTableOffset, 1)
		#decrease it with the number of invalid proficiencies
		if ProfName > 0x1000000 or ProfName <= 0:
			ProfCount -= 1

		#we only need the low 3 bits for proficiencies on levelup; otherwise
		#we just set them all to 0
		currentprof = 0
		if proftype == LUPROFS_TYPE_LEVELUP:
			stat = ProfsTable.GetValue (i+ProfsTableOffset, 0)
			if GameCheck.IsBG1():
				stat = stat + IE_PROFICIENCYBASTARDSWORD
			currentprof = GemRB.GetPlayerStat (pc, stat)&0x07
		else:
			#rangers always get 2 points in 2 weapons style
			if (i+ProfsTableOffset) == TwoWeapIndex and "RANGER" in ClassName.split("_"):
				currentprof = 2
		GemRB.SetVar ("Prof "+str(i), currentprof)
		GemRB.SetVar ("ProfBase "+str(i), currentprof)

		#see if we can assign to this prof
		if ClassWeaponsTable:
			# this table has profs as rows, so ignore the weird use of the column var
			# it also has different ordering than ProfsTable
			col = ClassWeaponsTable.GetColumnIndex (ProfsTable.GetRowName(i))
			maxprof = ClassWeaponsTable.GetValue (ProfsColumn, col)
			# the above is underreporting for lvl1 fighters, but it doesn't matter,
			# since there are enough prof types and the relevant code does use profsmax.2da
		else:
			maxprof = ProfsTable.GetValue(i+ProfsTableOffset, ProfsColumn)
		if maxprof > currentprof:
			ProfsAssignable += maxprof-currentprof

	#correct the profs left if we can't assign that much
	if ProfsPointsLeft > ProfsAssignable:
		ProfsPointsLeft = ProfsAssignable
	GemRB.SetVar ("ProfsPointsLeft", ProfsPointsLeft)

	# setup the +/- and info controls
	for i in range (ProfsNumButtons):
		if ProfsOffsetPress != -1:
			Button=ProfsWindow.GetControl(i+ProfsOffsetPress)
			Button.SetVarAssoc("Prof", i)
			Button.OnPress (ProfsJustPress)

		cid = i*2+ProfsOffsetButton1
		if Profs2ndOffsetButton1 != -1 and i > 7:
			cid = (i-8)*2+Profs2ndOffsetButton1

		Button=ProfsWindow.GetControl(cid)
		Button.SetVarAssoc("Prof", i)
		Button.OnPress (ProfsLeftPress)
		Button.SetActionInterval (200)

		Button=ProfsWindow.GetControl(cid+1)
		Button.SetVarAssoc("Prof", i)
		Button.OnPress (ProfsRightPress)
		Button.SetActionInterval (200)

	value = ProfsRedraw (0)
	if(ProfsScrollBar):
		# proficiencies scrollbar
		ProfsScrollBar.OnChange (lambda sb: ProfsRedraw (sb.Value))
		Button = ProfsWindow.GetControl(ProfsOffsetPress)
		AddScrollbarProxy(ProfsWindow, ProfsScrollBar, Button)
		ProfsScrollBar.SetVarAssoc ("ProfsTopIndex", value, 0, ProfCount)
	return

def ProfsRedraw (ProfsTopIndex):
	"""Redraws the proficiencies part of the window starting from ProfsTopIndex."""

	ProfSumLabel = ProfsWindow.GetControl(0x10000000+ProfsOffsetSum)
	ProfSumLabel.SetText(str(ProfsPointsLeft))
	SkipProfs = []

	for i in range(ProfsNumButtons):
		Pos=ProfsTopIndex+i
		ProfName = ProfsTable.GetValue(Pos+ProfsTableOffset, 1) #we add the bg1 skill count offset
		if ClassWeaponsTable: # iwd
			MaxProf = ClassWeaponsTable.GetValue (ClassNameSave, ProfsTable.GetRowName(Pos))
		else:
			MaxProf = ProfsTable.GetValue(Pos+ProfsTableOffset, ProfsColumn)
			ProfName = ProfsTable.GetValue(Pos+ProfsTableOffset, 1)
			if ProfName > 0x1000000 or ProfName < 0:
				MaxProf = 0

		cid = i*2+ProfsOffsetButton1
		if Profs2ndOffsetButton1 != -1 and i > 7:
			cid = (i-8)*2+Profs2ndOffsetButton1
		Button1=ProfsWindow.GetControl (cid)
		Button2=ProfsWindow.GetControl (cid+1)
		if MaxProf == 0:
			Button1.SetState(IE_GUI_BUTTON_DISABLED)
			Button2.SetState(IE_GUI_BUTTON_DISABLED)
			Button1.SetFlags(IE_GUI_BUTTON_NO_IMAGE,OP_OR)
			Button2.SetFlags(IE_GUI_BUTTON_NO_IMAGE,OP_OR)
			if GameCheck.IsBG2OrEE () and (i == 0 or ((i - 1) in SkipProfs)):
				SkipProfs.append (i)
		else:
			Button1.SetState(IE_GUI_BUTTON_ENABLED)
			Button2.SetState(IE_GUI_BUTTON_ENABLED)
			Button1.SetFlags(IE_GUI_BUTTON_NO_IMAGE,OP_NAND)
			Button2.SetFlags(IE_GUI_BUTTON_NO_IMAGE,OP_NAND)

		cid = 0x10000000 + ProfsOffsetLabel + i
		if Profs2ndOffsetLabel != -1 and i > 7:
			if OddIDs:
				cid = 0x10000000 + Profs2ndOffsetLabel + 2*(i - 8)
			else:
				cid = 0x10000000 + Profs2ndOffsetLabel + i - 8

		Label=ProfsWindow.GetControl (cid)
		Label.SetText(ProfName)

		ActPoint = GemRB.GetVar("Prof "+str(Pos) )
		for j in range(5): #5 is maximum distributable
			cid = i*5 + j + ProfsOffsetStar
			if Profs2ndOffsetStar != -1 and i > 7:
				cid = (i-8)*5 + j + Profs2ndOffsetStar
			Star=ProfsWindow.GetControl (cid)
			Star.SetSprites("GUIPFC", 0, 0, 0, 0, 0)
			if ActPoint > j:
				Star.SetFlags(IE_GUI_BUTTON_NO_IMAGE,OP_NAND)
			else:
				Star.SetFlags(IE_GUI_BUTTON_NO_IMAGE,OP_OR)

	return ProfsTopIndex + SkipProfs[-1] + 1 if len(SkipProfs) else ProfsTopIndex

def ProfsJustPress (btn):
	"""Updates the text area with a description of the proficiency."""
	Pos = btn.Value + GemRB.GetVar ("ProfsTopIndex")
	ProfsTextArea.SetText (ProfsTable.GetValue(Pos+ProfsTableOffset, 2) )
	return

def ProfsRightPress (btn):
	"""Decrease the current proficiency by one."""

	global ProfsPointsLeft

	Pos = btn.Value + GemRB.GetVar ("ProfsTopIndex")
	ProfsTextArea.SetText(ProfsTable.GetValue(Pos+ProfsTableOffset, 2) )
	ActPoint = GemRB.GetVar("Prof "+str(Pos) )
	MinPoint = GemRB.GetVar ("ProfBase "+str(Pos) )
	if ActPoint <= 0 or ActPoint <= MinPoint:
		return
	GemRB.SetVar("Prof "+str(Pos),ActPoint-1)
	ProfsPointsLeft += 1
	GemRB.SetVar ("ProfsPointsLeft", ProfsPointsLeft)
	ProfsRedraw (GemRB.GetVar("ProfsTopIndex"))
	ProfsCallback ()
	return

def ProfsLeftPress (btn):
	"""Increases the current proficiency by one."""

	global ProfsPointsLeft

	Pos = btn.Value + GemRB.GetVar("ProfsTopIndex")
	ProfsTextArea.SetText(ProfsTable.GetValue(Pos+ProfsTableOffset, 2) )
	if ProfsPointsLeft == 0:
		return
	if GameCheck.IsIWD1() or GameCheck.IsBG1():
		ProfMaxTable = GemRB.LoadTable ("profsmax")
		if ProfsType == LUPROFS_TYPE_CHARGEN:
			MaxProf = ProfMaxTable.GetValue(ClassNameSave, "FIRST_LEVEL")
		else:
			MaxProf = ProfMaxTable.GetValue(ClassNameSave, "OTHER_LEVELS")
	else:
		MaxProf = ProfsTable.GetValue(Pos+ProfsTableOffset, ProfsColumn)
	if MaxProf>5:
		MaxProf = 5

	ActPoint = GemRB.GetVar("Prof "+str(Pos) )
	if ActPoint >= MaxProf:
		return
	GemRB.SetVar("Prof "+str(Pos),ActPoint+1)
	ProfsPointsLeft -= 1
	GemRB.SetVar ("ProfsPointsLeft", ProfsPointsLeft)
	ProfsRedraw (GemRB.GetVar("ProfsTopIndex"))
	ProfsCallback ()
	return

def ProfsSave (pc, proftype = LUPROFS_TYPE_LEVELUP):
	"""Updates the actor with the new proficiencies."""

	ProfCount = ProfsTable.GetRowCount () - ProfsTableOffset
	for i in range(ProfCount): # skip bg1 weapprof.2da proficiencies
		ProfID = ProfsTable.GetValue (i+ProfsTableOffset, 0)
		if GameCheck.IsBG1():
			ProfID = ProfID + IE_PROFICIENCYBASTARDSWORD
		SaveProf = GemRB.GetVar ("Prof "+str(i))

		if proftype == LUPROFS_TYPE_CHARGEN and GameCheck.IsBG2OrEE ():
			GemRB.DispelEffect (pc, "Proficiency", ProfID)
		elif proftype != LUPROFS_TYPE_DUALCLASS:
			OldProf = GemRB.GetPlayerStat (pc, ProfID) & 0x38
			SaveProf = OldProf | SaveProf
		else: # gotta move the old prof to the back for dual class
			OldProf = GemRB.GetPlayerStat (pc, ProfID) & 0x07
			SaveProf = (OldProf << 3) | SaveProf

		GemRB.SetPlayerStat (pc, ProfID, SaveProf)
		if GameCheck.IsBG2OrEE () and proftype != LUPROFS_TYPE_DUALCLASS and SaveProf:
			GemRB.ApplyEffect (pc, "Proficiency", SaveProf, ProfID)
	return

def ProfsNullify ():
	"""Resets all of the internal variables to 0."""

	global ProfsTable
	if not ProfsTable:
		ProfsTable = GemRB.LoadTable ("weapprof")
	for i in range (ProfsTable.GetRowCount()-ProfsTableOffset+1): #skip bg1 profs
		GemRB.SetVar ("Prof "+str(i), 0)
		GemRB.SetVar ("ProfBase "+str(i), 0)
	return
