import calendar
from datetime import datetime
from datetime import timedelta
from direct.directnotify import DirectNotifyGlobal
from direct.fsm.FSM import FSM
from direct.gui import DirectGuiGlobals
from direct.gui.DirectGui import DirectFrame, DirectButton, DirectLabel, DirectScrolledList, DirectCheckButton
from direct.showbase import DirectObject
from direct.showbase import PythonUtil
from panda3d.core import *

from otp.otpbase import OTPGlobals
from otp.otpbase import OTPLocalizer
from otp.nametag.NametagGroup import NametagGroup
from otp.nametag.Nametag import Nametag
from otp.nametag.NametagFloat2d import *
from toontown.parties import PartyGlobals
from toontown.parties import PartyUtils
from toontown.parties.CalendarGuiMonth import CalendarGuiMonth
from toontown.parties.InviteVisual import InviteVisual
from toontown.parties.PartyEditor import PartyEditor
from toontown.parties.PartyInfo import PartyInfo
from toontown.parties.ScrolledFriendList import ScrolledFriendList
from toontown.toon import ToonHead
from toontown.toonbase import TTLocalizer
from toontown.toonbase import ToontownGlobals
from toontown.toontowngui import TTDialog


class PartyPlanner(DirectFrame, FSM):
    notify = DirectNotifyGlobal.directNotify.newCategory('PartyPlanner')

    def __init__(self, doneEvent = None):
        FSM.__init__(self, 'PartyPlannerFSM')
        DirectFrame.__init__(self)
        self.doneEvent = doneEvent
        self.stateArray = ['Off',
         'Welcome',
         'PartyEditor', # 'Guests',  skip over the Guests state.
         'Date',
         'Time',
         'Invitation',
         'Farewell']
        self.partyTime = base.cr.toontownTimeManager.getCurServerDateTime()
        self.partyNowTime = base.cr.toontownTimeManager.getCurServerDateTime()
        minutesToNextFifteen = 15 - self.partyTime.minute % 15
        self.cleanPartyTime = self.partyTime + timedelta(minutes=minutesToNextFifteen, seconds=-self.partyTime.second)
        self.partyTime = self.cleanPartyTime
        self.guests = []
        self.isPrivate = False
        self.selectedCalendarGuiDay = None
        self.gui = loader.loadModel('phase_4/models/parties/partyPlannerGUI')
        self.partyDuration = timedelta(hours=PartyGlobals.DefaultPartyDuration)
        self.timeTypeToMaxValue = {'hour': 23,
         'minute': 59}
        self.timeTypeToChangeAmount = {'hour': (1, -1),
         'minute': (15, -15),
         'ampm': (1, -1)}
        self.partyInfo = None
        self.asapMinuteRounding = base.config.GetInt('party-asap-minute-rounding', PartyGlobals.PartyPlannerAsapMinuteRounding)
        self.load()
        self.request('Welcome')
        return

    def enterWelcome(self, *args):
        self.prevButton['state'] = DirectGuiGlobals.DISABLED
        self.prevButton.hide()
        self.nextButton['state'] = DirectGuiGlobals.NORMAL
        self.welcomePage.show()
        self.partyPlannerHead.reparentTo(self.welcomePage)
        self.partyPlannerHead.startBlink()
        self.partyPlannerHead.startLookAround()
        self.nametagNP.reparentTo(self.welcomePage)
        self.chatNP.reparentTo(self.welcomePage)

    def exitWelcome(self):
        self.welcomePage.hide()
        self.prevButton.show()
        self.partyPlannerHead.stopBlink()
        self.partyPlannerHead.stopLookAround()

    def enterPartyEditor(self, *args):
        self.prevButton['state'] = DirectGuiGlobals.NORMAL
        self.nextButton['state'] = DirectGuiGlobals.DISABLED
        self.nextButton.hide()
        self.partyEditorPage.show()
        self.okWithGroundsGui.doneStatus = ''
        self.partyEditor.request('Idle')

    def exitPartyEditor(self):
        self.partyEditor.request('Hidden')
        self.partyEditorPage.hide()

    def enterGuests(self, *args):
        self.prevButton['state'] = DirectGuiGlobals.NORMAL
        self.nextButton['state'] = DirectGuiGlobals.NORMAL
        self.nextButton.show()
        self.guestPage.show()

    def exitGuests(self):
        self.guests = []
        for friendCheckBox in self.friendList['items']:
            if friendCheckBox['indicatorValue']:
                self.guests.append(friendCheckBox.getPythonTag('id'))

        self.guestPage.hide()

    def enterDate(self, *args):
        self.prevButton.show()
        self.prevButton['state'] = DirectGuiGlobals.NORMAL
        if self.selectedCalendarGuiDay is None:
            self.nextButton['state'] = DirectGuiGlobals.DISABLED
            self.nextButton.hide()
            self.makePartyNowButton.show()
        self.datePage.show()
        return

    def exitDate(self):
        self.datePage.hide()
        self.nextButton.show()
        if self.selectedCalendarGuiDay is not None:
            self.partyTime = self.cleanPartyTime
            self.alterPartyTime(year=self.selectedCalendarGuiDay.myDate.year, month=self.selectedCalendarGuiDay.myDate.month, day=self.selectedCalendarGuiDay.myDate.day)
        else:
            self.partyNowTime = self.calcAsapTime()
            self.partyTime = self.partyNowTime
        return

    def calcAsapTime(self):
        curServerTime = base.cr.toontownTimeManager.getCurServerDateTime()
        baseTime = curServerTime
        baseTime = baseTime.replace(baseTime.year, baseTime.month, baseTime.day, baseTime.hour, baseTime.minute, second=0, microsecond=0)
        minute = curServerTime.minute
        remainder = minute % self.asapMinuteRounding
        if remainder:
            baseTime += timedelta(minutes=self.asapMinuteRounding - remainder)
        else:
            baseTime += timedelta(minutes=self.asapMinuteRounding)
        return baseTime

    def enterTime(self, *args):
        self.prevButton.show()
        self.prevButton['state'] = DirectGuiGlobals.NORMAL
        self.nextButton.show()
        self.timePage.show()
        self.timePageRecapToontownTimeLabel2['text'] = '%s' % PartyUtils.formatDateTime(self.partyTime)
        self.timePageRecapLocalTimeLabel['text'] = '%s%s' % (TTLocalizer.PartyPlannerTimeLocalTime, PartyUtils.formatDateTime(self.partyTime, inLocalTime=True))

    def exitTime(self):
        self.timePage.hide()
        self.nextButton.show()

    def enterInvitation(self, *args):
        self.prevButton['state'] = DirectGuiGlobals.NORMAL
        self.nextButton.hide()
        defaultInviteTheme = PartyGlobals.InviteTheme.GenericMale
        if base.cr.newsManager.isHolidayRunning(ToontownGlobals.VICTORY_PARTY_HOLIDAY):
            defaultInviteTheme = PartyGlobals.InviteTheme.VictoryParty
        elif base.cr.newsManager.isHolidayRunning(ToontownGlobals.KARTING_TICKETS_HOLIDAY) or base.cr.newsManager.isHolidayRunning(ToontownGlobals.GRAND_PRIX):
            defaultInviteTheme = PartyGlobals.InviteTheme.Racing
        elif base.cr.newsManager.isHolidayRunning(ToontownGlobals.VALENTOONS_DAY):
            defaultInviteTheme = PartyGlobals.InviteTheme.Valentoons
        if self.partyInfo is not None:
            del self.partyInfo
        activityList = self.partyEditor.partyEditorGrid.getActivitiesOnGrid()
        decorationList = self.partyEditor.partyEditorGrid.getDecorationsOnGrid()
        endTime = self.partyTime + self.partyDuration
        self.partyInfo = PartyInfo(0, 0, self.partyTime.year, self.partyTime.month, self.partyTime.day, self.partyTime.hour, self.partyTime.minute, endTime.year, endTime.month, endTime.day, endTime.hour, endTime.minute, self.isPrivate, defaultInviteTheme, activityList, decorationList, 0)
        if self.noFriends or len(self.getInvitees()) == 0:
            self.inviteVisual.setNoFriends(True)
            self.invitationTitleLabel['text'] = TTLocalizer.PartyPlannerConfirmTitleNoFriends
            self.inviteButton['text'] = TTLocalizer.PartyPlannerInviteButtonNoFriends
            self.selectedInviteThemeLabel.stash()
            self.nextThemeButton.stash()
            self.prevThemeButton.stash()
            self.setInviteTheme(defaultInviteTheme)
        else:
            self.inviteVisual.setNoFriends(False)
            self.invitationTitleLabel['text'] = TTLocalizer.PartyPlannerConfirmTitle
            self.inviteButton['text'] = TTLocalizer.PartyPlannerInviteButton
            self.selectedInviteThemeLabel.unstash()
            self.nextThemeButton.unstash()
            self.prevThemeButton.unstash()
            self.setInviteTheme(defaultInviteTheme)
        self.inviteVisual.updateInvitation(base.localAvatar.getName(), self.partyInfo)
        self.invitationPage.show()
        return

    def __prevTheme(self):
        self.nextThemeButton.show()
        prevTheme = self.currentInvitationTheme - 1
        while prevTheme not in self.inviteThemes:
            prevTheme -= 1
            if prevTheme == self.currentInvitationTheme:
                self.notify.warning('No previous invite theme found.')
                break
            elif prevTheme < 0:
                prevTheme = len(self.inviteVisual.inviteThemesIdToInfo) - 1

        self.setInviteTheme(prevTheme)

    def __nextTheme(self):
        self.prevThemeButton.show()
        nextTheme = self.currentInvitationTheme + 1
        while nextTheme not in self.inviteThemes:
            nextTheme += 1
            if nextTheme == self.currentInvitationTheme:
                self.notify.warning('No next invite theme found.')
                break
            elif nextTheme >= len(self.inviteVisual.inviteThemesIdToInfo):
                nextTheme = 0

        self.setInviteTheme(nextTheme)

    def setInviteTheme(self, themeNumber):
        self.currentInvitationTheme = themeNumber
        self.selectedInviteThemeLabel['text'] = '%s %s (%d/%d)' % (self.inviteVisual.inviteThemesIdToInfo[self.currentInvitationTheme][1],
         TTLocalizer.PartyPlannerInvitationTheme,
         self.inviteThemes.index(self.currentInvitationTheme) + 1,
         len(self.inviteThemes))
        self.partyInfo.inviteTheme = self.currentInvitationTheme
        self.inviteVisual.updateInvitation(base.localAvatar.getName(), self.partyInfo)

    def exitInvitation(self):
        self.invitationPage.hide()
        self.nextButton.show()

    def enterFarewell(self, goingBackAllowed):
        self.farewellPage.show()
        if goingBackAllowed:
            self.prevButton.show()
        else:
            self.prevButton.hide()
        self.nextButton.hide()
        self.partyPlannerHead.reparentTo(self.farewellPage)
        self.partyPlannerHead.startBlink()
        self.partyPlannerHead.startLookAround()
        self.nametagNP.reparentTo(self.farewellPage)
        self.chatNP.reparentTo(self.farewellPage)

    def exitFarewell(self):
        self.farewellPage.hide()
        self.nextButton.show()
        self.prevButton.show()
        self.partyPlannerHead.stopBlink()
        self.partyPlannerHead.stopLookAround()

    def load(self):
        self.frame = DirectFrame(parent=aspect2d, geom=self.gui.find('**/background'), relief=None, scale=0.85, pos=(0.05, 0.0, 0.1))
        self.titleScale = TTLocalizer.PPtitleScale
        self._createNavButtons()
        self.welcomePage = self._createWelcomePage()
        self.welcomePage.hide()
        self.datePage = self._createDatePage()
        self.datePage.hide()
        self.timePage = self._createTimePage()
        self.timePage.hide()
        self.guestPage = self._createGuestPage()
        self.guestPage.hide()
        self.partyEditorPage = self._createPartyEditorPage()
        self.partyEditorPage.hide()
        self.invitationPage = self._createInvitationPage()
        self.invitationPage.hide()
        self.farewellPage = self._createFarewellPage()
        self.farewellPage.hide()
        return

    def _createNavButtons(self):
        self.quitButton = DirectButton(parent=self.frame, relief=None, geom=(self.gui.find('**/cancelButton_up'), self.gui.find('**/cancelButton_down'), self.gui.find('**/cancelButton_rollover')), command=self.__acceptExit)
        self.nextButton = DirectButton(parent=self.frame, relief=None, geom=(self.gui.find('**/bottomNext_button/nextButton_up'), self.gui.find('**/bottomNext_button/nextButton_down'), self.gui.find('**/bottomNext_button/nextButton_rollover')), command=self.__nextItem, state=DirectGuiGlobals.DISABLED)
        self.prevButton = DirectButton(parent=self.frame, relief=None, geom=(self.gui.find('**/bottomPrevious_button/previousButton_up'), self.gui.find('**/bottomPrevious_button/previousButton_down'), self.gui.find('**/bottomPrevious_button/previousButton_rollover')), command=self.__prevItem, state=DirectGuiGlobals.DISABLED)
        self.currentItem = None
        return

    def __createNametag(self, parent):
        if self.nametagGroup == None:
            self.nametagGroup = NametagGroup()
            self.nametagGroup.setFont(ToontownGlobals.getToonFont())
            self.nametagGroup.setSpeechFont(ToontownGlobals.getToonFont())
            self.nametagGroup.setActive(0)
            self.nametagGroup.setAvatar(self.partyPlannerHead)
            self.nametagGroup.manage(base.marginManager)
            self.nametagGroup.setColorCode(self.nametagGroup.CCNonPlayer)
            self.nametagGroup.getNametag2d().setContents(0)
            self.nametagNode = NametagFloat2d()
            self.nametagNode.setContents(Nametag.CName)
            self.nametagGroup.addNametag(self.nametagNode)
            self.nametagGroup.setName(base.cr.partyManager.getPartyPlannerName())
            self.nametagNP = parent.attachNewNode(self.nametagNode)
            nametagPos = self.gui.find('**/step_01_partymanPeteNametag_locator').getPos()
            self.nametagNP.setPosHprScale(nametagPos[0], 0, nametagPos[2], 0, 0, 0, 0.1, 1, 0.1)
            self.chatNode = NametagFloat2d()
            self.chatNode.setContents(Nametag.CSpeech | Nametag.CThought)
            self.nametagGroup.addNametag(self.chatNode)
            self.nametagGroup.setChat(TTLocalizer.PartyPlannerInstructions, CFSpeech)
            self.chatNP = parent.attachNewNode(self.chatNode)
            chatPos = self.gui.find('**/step_01_partymanPeteText_locator').getPos()
            self.chatNP.setPosHprScale(chatPos[0], 0, chatPos[2], 0, 0, 0, 0.08, 1, 0.08)

    def clearNametag(self):
        if self.nametagGroup != None:
            self.nametagGroup.unmanage(base.marginManager)
            self.nametagGroup.removeNametag(self.nametagNode)
            self.nametagGroup.removeNametag(self.chatNode)
            self.nametagNP.removeNode()
            self.chatNP.removeNode()
            del self.nametagNP
            del self.chatNP
            del self.nametagNode
            del self.chatNode
            self.nametagGroup.setAvatar(NodePath())
            self.nametagGroup.destroy()
            self.nametagGroup = None
        return

    def _createWelcomePage(self):
        self.nametagGroup = None
        page = DirectFrame(self.frame)
        page.setName('PartyPlannerWelcomePage')
        self.welcomeTitleLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerWelcomeTitle, pos=self.gui.find('**/title_locator').getPos(), scale=self.titleScale)
        self.partyPlannerHead = ToonHead.ToonHead()
        partyPlannerStyle = base.cr.partyManager.getPartyPlannerStyle()
        self.partyPlannerHead.setupHead(partyPlannerStyle, forGui=True)
        self.partyPlannerHead.setPos(self.gui.find('**/step_01_partymanPete_locator').getPos())
        animal = partyPlannerStyle.getAnimal()
        if animal == 'cat' or animal == 'pig':
            headScale = 0.4
        elif animal == 'dog' or animal == 'bear':
            headScale = 0.45
        elif animal == 'rabbit':
            headScale = 0.35
        else:
            headScale = 0.3
        self.partyPlannerHead.setScale(headScale)
        self.partyPlannerHead.setH(180.0)
        self.partyPlannerHead.reparentTo(page)
        self.__createNametag(page)
        return page

    def _createDatePage(self):
        page = DirectFrame(self.frame)
        page.setName('PartyPlannerDatePage')
        self.createDateTitleLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerDateTitle, pos=self.gui.find('**/title_locator').getPos(), scale=self.titleScale)
        pos = self.gui.find('**/step_06_sendInvitation_locator').getPos()
        self.makePartyNowButton = DirectButton(parent=page, relief=None, geom=(self.gui.find('**/send_up'), self.gui.find('**/send_down'), self.gui.find('**/send_rollover')), text=TTLocalizer.PartyPlannerPartyNow, text_pos=(pos[0], pos[2]), text_scale=0.05, command=self.__doMakePartyNow)
        curServerDate = base.cr.toontownTimeManager.getCurServerDateTime()
        self.calendarGuiMonth = CalendarGuiMonth(page, curServerDate, scale=0.95, pos=(-0.05, 0.0, -0.33), dayClickCallback=self._dayClickCallback, onlyFutureDaysClickable=True)
        return page

    def __doMakePartyNow(self):
        self.request('Invitation')

    def _dayClickCallback(self, calendarGuiDay):
        self.selectedCalendarGuiDay = calendarGuiDay
        self.nextButton['state'] = DirectGuiGlobals.NORMAL
        self.makePartyNowButton.hide()
        self.nextButton.show()

    def alterPartyTime(self, year = None, month = None, day = None, hour = None, minute = None):
        self.partyTime = datetime(year=self.positiveTime('year', year), month=self.positiveTime('month', month), day=self.positiveTime('day', day), hour=self.positiveTime('hour', hour), minute=self.positiveTime('minute', minute), tzinfo=self.partyTime.tzinfo)

    def positiveTime(self, type, amount):
        if amount is None:
            return getattr(self.partyTime, type)
        if type == 'hour' or type == 'minute':
            if amount < 0:
                return self.timeTypeToMaxValue[type] + 1 + self.timeTypeToChangeAmount[type][1]
            elif amount > self.timeTypeToMaxValue[type]:
                return 0
        return amount

    def _createTimePage(self):
        page = DirectFrame(self.frame)
        page.setName('PartyPlannerTimePage')
        self.createTimeTitleLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerTimeTitle, pos=self.gui.find('**/title_locator').getPos(), scale=self.titleScale)
        self.clockImage = DirectFrame(parent=page, relief=None, geom=self.gui.find('**/toontownTime_background'))
        self.timePageToontownLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerTimeToontown, pos=self.gui.find('**/step_03_toontown_locator').getPos(), scale=0.15, text_fg=(1.0, 0.0, 0.0, 1.0), text_font=ToontownGlobals.getSignFont())
        self.timePageTimeLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerTimeTime, pos=self.gui.find('**/step_03_time_locator').getPos(), scale=0.15, text_fg=(1.0, 0.0, 0.0, 1.0), text_font=ToontownGlobals.getSignFont())
        self.timePageRecapLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerTimeRecap, pos=self.gui.find('**/step_03_partyDateAndTime_locator').getPos(), scale=0.09)
        self.timePageRecapToontownTimeLabel1 = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerTimeToontownTime, pos=self.gui.find('**/step_03_toontownTime_locator').getPos(), scale=0.06)
        self.timePageRecapToontownTimeLabel2 = DirectLabel(parent=page, relief=None, text='%s' % PartyUtils.formatDateTime(self.partyTime), pos=self.gui.find('**/step_03_toontownDateAndTime_loactor').getPos(), textMayChange=True, scale=0.06)
        self.timePageRecapLocalTimeLabel = DirectLabel(parent=page, relief=None, text='%s%s' % (TTLocalizer.PartyPlannerTimeLocalTime, PartyUtils.formatDateTime(self.partyTime, inLocalTime=True)), pos=self.gui.find('**/step_03_localDateAndTime_loactor').getPos(), textMayChange=True, scale=0.06, text_fg=(1.0, 0.0, 0.0, 1.0))
        self.timeInputHourLabel, self.timeInputHourUpButton, self.timeInputHourDownButton = self.getTimeWidgets(page, 'hour')
        self.timeInputMinuteLabel, self.timeInputMinuteUpButton, self.timeInputMinuteDownButton = self.getTimeWidgets(page, 'minute')
        self.timeInputAmPmLabel, self.timeInputAmPmUpButton, self.timeInputAmPmDownButton = self.getTimeWidgets(page, 'ampm')
        self.timePagecolonLabel = DirectLabel(parent=page, relief=None, text=':', pos=self.gui.find('**/step_03_colon_locator').getPos(), scale=0.15)
        return page

    def getTimeWidgets(self, page, type):
        if type == 'ampm':
            data = self.getCurrentAmPm()
        else:
            data = getattr(self.partyTime, type)
            if data == 0 and type == 'minute':
                data = '00'
            else:
                if type == 'hour':
                    data = data % 12
                    if data == 0:
                        data = 12
                data = '%d' % data
        label = DirectLabel(parent=page, relief=None, text='%s' % data, textMayChange=True, pos=self.gui.find('**/step_03_%s_locator' % type).getPos(), scale=0.12)

        def changeValue(self, amount):
            if type == 'ampm':
                self.alterPartyTime(hour=(self.partyTime.hour + 12) % 24)
                newAmount = self.getCurrentAmPm()
                label['text'] = newAmount
            else:
                if type == 'hour':
                    newAmount = getattr(self.partyTime, type) + amount
                    newAmount = newAmount % 12
                    if self.timeInputAmPmLabel['text'] == TTLocalizer.PartyTimeFormatMeridiemPM:
                        newAmount = newAmount % 12 + 12
                    self.alterPartyTime(hour=newAmount)
                elif type == 'minute':
                    newAmount = getattr(self.partyTime, type) + amount
                    self.alterPartyTime(minute=newAmount)
                else:
                    PartyPlanner.notify.error('Invalid type for changeValue in PartyPlanner: %s' % type)
                newAmount = getattr(self.partyTime, type)
                if newAmount < 10 and type == 'minute':
                    label['text'] = '0%d' % newAmount
                else:
                    if type == 'hour':
                        newAmount = newAmount % 12
                        if newAmount == 0:
                            newAmount = 12
                    label['text'] = '%d' % newAmount
            self.timePageRecapToontownTimeLabel2['text'] = '%s' % PartyUtils.formatDateTime(self.partyTime)
            self.timePageRecapLocalTimeLabel['text'] = '%s%s' % (TTLocalizer.PartyPlannerTimeLocalTime, PartyUtils.formatDateTime(self.partyTime, inLocalTime=True))

        upButton = DirectButton(parent=page, relief=None, geom=(self.gui.find('**/%sButtonUp_up' % type), self.gui.find('**/%sButtonUp_down' % type), self.gui.find('**/%sButtonUp_rollover' % type)), command=changeValue, extraArgs=[self, self.timeTypeToChangeAmount[type][0]])
        downButton = DirectButton(parent=page, relief=None, geom=(self.gui.find('**/%sButtonDown_up' % type), self.gui.find('**/%sButtonDown_down' % type), self.gui.find('**/%sButtonDown_rollover' % type)), command=changeValue, extraArgs=[self, self.timeTypeToChangeAmount[type][1]])
        return (label, upButton, downButton)

    def getCurrentAmPm(self):
        if self.partyTime.hour < 12:
            return TTLocalizer.PartyTimeFormatMeridiemAM
        else:
            return TTLocalizer.PartyTimeFormatMeridiemPM

    def _createGuestPage(self):
        page = DirectFrame(self.frame)
        page.setName('PartyPlannerGuestPage')
        self.guestTitleLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerGuestTitle, pos=self.gui.find('**/title_locator').getPos(), scale=self.titleScale)
        self.guestBackgroundLabel = DirectLabel(parent=page, relief=None, image=self.gui.find('**/guestListBackground_flat'), scale=(1.2, 1.0, 1.0))
        self.friendList = ScrolledFriendList(page, self.gui, makeItemsCheckBoxes=True)
        if len(base.localAvatar.friendsList) == 0:
            self.noFriends = True
        else:
            self.noFriends = False
            for friendId in base.localAvatar.friendsList:
                self.friendList.addFriend(friendId)

            self.friendList.scrollTo(0)
        pos = self.gui.find('**/step_04_partyWillBe_locator').getPos()
        self.publicPrivateLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerPublicPrivateLabel, text_align=TextNode.ACenter, text_scale=0.065, pos=pos)
        self.publicDescriptionLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerPublicDescription, text_align=TextNode.ACenter, text_scale=TTLocalizer.PPpbulicDescriptionLabel, pos=(pos[0] - 0.52, pos[1], pos[2]))
        self.publicDescriptionLabel.stash()
        self.privateDescriptionLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerPrivateDescription, text_align=TextNode.ACenter, text_scale=TTLocalizer.PPprivateDescriptionLabel, pos=(pos[0] + 0.55, pos[1], pos[2]))
        self.privateDescriptionLabel.stash()
        pos = self.gui.find('**/step_04_public_locator').getPos()
        self.publicButton = DirectButton(parent=page, relief=None, geom=(self.gui.find('**/publicButton_up'),
         self.gui.find('**/publicButton_down'),
         self.gui.find('**/publicButton_rollover'),
         self.gui.find('**/publicButton_inactive')), text=TTLocalizer.PartyPlannerPublic, text_pos=(pos[0], pos[2]), text_scale=TTLocalizer.PPpublicButton, command=self.__doTogglePublicPrivate)
        self.publicButton['state'] = DirectGuiGlobals.DISABLED
        self.publicButton.bind(DirectGuiGlobals.ENTER, self.__enterPublic)
        self.publicButton.bind(DirectGuiGlobals.EXIT, self.__exitPublic)
        pos = self.gui.find('**/step_04_private_locator').getPos()
        self.privateButton = DirectButton(parent=page, relief=None, geom=(self.gui.find('**/privateButton_up'),
         self.gui.find('**/privateButton_down'),
         self.gui.find('**/privateButton_rollover'),
         self.gui.find('**/privateButton_inactive')), text=TTLocalizer.PartyPlannerPrivate, text_pos=(pos[0], pos[2]), text_scale=TTLocalizer.PPprivateButton, command=self.__doTogglePublicPrivate)
        self.privateButton.bind(DirectGuiGlobals.ENTER, self.__enterPrivate)
        self.privateButton.bind(DirectGuiGlobals.EXIT, self.__exitPrivate)
        self.checkAllButton = DirectButton(parent=page, relief=None, geom=(self.gui.find('**/checkAllButton_up'), self.gui.find('**/checkAllButton_down'), self.gui.find('**/checkAllButton_rollover')), command=self.__doCheckAll)
        self.uncheckAllButton = DirectButton(parent=page, relief=None, geom=(self.gui.find('**/uncheckAllButton_up'), self.gui.find('**/uncheckAllButton_down'), self.gui.find('**/uncheckAllButton_rollover')), command=self.__doUncheckAll)
        return page

    def __doCheckAll(self):
        for friendBox in self.friendList['items']:
            friendBox['indicatorValue'] = True

    def __doUncheckAll(self):
        for friendBox in self.friendList['items']:
            friendBox['indicatorValue'] = False

    def __enterPrivate(self, mouseEvent):
        self.privateDescriptionLabel.unstash()

    def __exitPrivate(self, mouseEvent):
        self.privateDescriptionLabel.stash()

    def __enterPublic(self, mouseEvent):
        self.publicDescriptionLabel.unstash()

    def __exitPublic(self, mouseEvent):
        self.publicDescriptionLabel.stash()

    def __doTogglePublicPrivate(self):
        if self.isPrivate:
            self.isPrivate = False
            self.privateButton['state'] = DirectGuiGlobals.NORMAL
            self.publicButton['state'] = DirectGuiGlobals.DISABLED
        else:
            self.isPrivate = True
            self.privateButton['state'] = DirectGuiGlobals.DISABLED
            self.publicButton['state'] = DirectGuiGlobals.NORMAL

    def _createPartyEditorPage(self):
        page = DirectFrame(self.frame)
        page.setName('PartyPlannerEditorPage')
        self.LayoutTitleLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerEditorTitle, pos=self.gui.find('**/title_locator').getPos() + Point3(0.0, 0.0, 0.075), scale=self.titleScale)
        self.costLabel = DirectLabel(parent=page, pos=(-0.74, 0.0, 0.17), relief=None, text=TTLocalizer.PartyPlannerTotalCost % 0, text_align=TextNode.ACenter, scale=TTLocalizer.PPcostLabel, textMayChange=True)
        self.partyGridBackground = DirectFrame(parent=page, relief=None, geom=self.gui.find('**/partyGrid_flat'))
        self.partyGroundsLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerPartyGrounds, text_font=ToontownGlobals.getSignFont(), text_fg=VBase4(1.0, 0.0, 0.0, 1.0), text_scale=TTLocalizer.PPpartyGroundsLabel, pos=self.gui.find('**/step_05_partyGrounds_text_locator').getPos(), scale=0.1)
        self.activityBackground = DirectFrame(parent=page, relief=None, geom=self.gui.find('**/activitiesDecorations_flat1'), pos=(0.0, 0.0, 0.04))
        pos = self.gui.find('**/step_05_instructions_locator').getPos()
        self.instructionLabel = DirectLabel(parent=page, relief=None, text=' ', text_pos=(pos[0], pos[2]), text_scale=TTLocalizer.PPinstructionLabel, textMayChange=True, geom=self.gui.find('**/instructions_flat'))
        self.elementTitleLabel = DirectLabel(parent=page, relief=None, text=' ', pos=self.gui.find('**/step_05_activitiesName_text_locator').getPos() + Point3(0.0, 0.0, 0.04), text_scale=TTLocalizer.PPelementTitleLabel, textMayChange=True)
        self.elementPriceNode = TextNode('ElementPrice')
        self.elementPriceNode.setAlign(TextNode.ALeft)
        self.elementPriceNode.setTextColor(0.0, 0.0, 0.0, 1.0)
        self.elementPriceNode.setFont(ToontownGlobals.getToonFont())
        self.elementPrice = page.attachNewNode(self.elementPriceNode)
        self.elementPrice.setScale(TTLocalizer.PPelementPriceNode)
        self.elementPrice.setPos(self.gui.find('**/step_05_activityPrice_text_locator').getPos() + Point3(-0.02, 0.0, 0.04))
        self.elementDescriptionNode = TextNode('ElementDescription')
        self.elementDescriptionNode.setAlign(TextNode.ACenter)
        self.elementDescriptionNode.setWordwrap(8)
        self.elementDescriptionNode.setFont(ToontownGlobals.getToonFont())
        self.elementDescriptionNode.setTextColor(0.0, 0.0, 0.0, 1.0)
        self.elementDescription = page.attachNewNode(self.elementDescriptionNode)
        self.elementDescription.setScale(TTLocalizer.PPelementDescription)
        self.elementDescription.setPos(self.gui.find('**/step_05_activityDescription_text_locator').getPos() + Point3(0.0, 0.0, 0.04))
        self.totalMoney = base.localAvatar.getTotalMoney()
        catalogGui = loader.loadModel('phase_5.5/models/gui/catalog_gui')
        self.beanBank = DirectLabel(parent=page, relief=None, text=str(self.totalMoney), text_align=TextNode.ARight, text_scale=0.075, text_fg=(0.95, 0.95, 0, 1), text_shadow=(0, 0, 0, 1), text_pos=(0.495, -0.53), text_font=ToontownGlobals.getSignFont(), textMayChange=True, image=catalogGui.find('**/bean_bank'), image_scale=(0.65, 0.65, 0.65), scale=0.9, pos=(-0.75, 0.0, 0.6))
        catalogGui.removeNode()
        del catalogGui
        self.accept(localAvatar.uniqueName('moneyChange'), self.__moneyChange)
        self.accept(localAvatar.uniqueName('bankMoneyChange'), self.__moneyChange)
        self.partyEditor = PartyEditor(self, page)
        self.partyEditor.request('Hidden')
        pos = self.gui.find('**/step_05_add_text_locator').getPos()
        self.elementBuyButton = DirectButton(parent=page, relief=None, text=TTLocalizer.PartyPlannerBuy, text_pos=(pos[0], pos[2]), text_scale=TTLocalizer.PPelementBuyButton, geom=(self.gui.find('**/add_up'), self.gui.find('**/add_down'), self.gui.find('**/add_rollover')), geom3_color=VBase4(0.5, 0.5, 0.5, 1.0), textMayChange=True, pos=(0.0, 0.0, 0.04), command=self.partyEditor.buyCurrentElement)
        self.okWithPartyGroundsLayoutEvent = 'okWithPartyGroundsLayoutEvent'
        self.accept(self.okWithPartyGroundsLayoutEvent, self.okWithPartyGroundsLayout)
        self.okWithGroundsGui = TTDialog.TTGlobalDialog(dialogName=self.uniqueName('PartyEditorOkGui'), doneEvent=self.okWithPartyGroundsLayoutEvent, message=TTLocalizer.PartyPlannerOkWithGroundsLayout, style=TTDialog.YesNo, okButtonText=OTPLocalizer.DialogYes, cancelButtonText=OTPLocalizer.DialogNo)
        self.okWithGroundsGui.doneStatus = ''
        self.okWithGroundsGui.hide()
        return page

    def okWithPartyGroundsLayout(self):
        self.okWithGroundsGui.hide()
        if self.okWithGroundsGui.doneStatus == 'ok':
            self.__nextItem()

    def setNextButtonState(self, enabled):
        if enabled:
            self.nextButton['state'] = DirectGuiGlobals.NORMAL
            self.nextButton.show()
        else:
            self.nextButton['state'] = DirectGuiGlobals.DISABLED
            self.nextButton.hide()

    def _createInvitationPage(self):
        self.__handleHolidays()
        page = DirectFrame(self.frame)
        page.setName('PartyPlannerInvitationPage')
        self.invitationTitleLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerConfirmTitle, textMayChange=True, pos=self.gui.find('**/title_locator').getPos(), scale=self.titleScale)
        self.invitationBackground = DirectFrame(parent=page, relief=None, geom=self.gui.find('**/invitationBackground'))
        self.inviteVisual = InviteVisual(page)
        self.selectedInviteThemeLabel = DirectLabel(parent=page, relief=None, pos=self.gui.find('**/step_06_theme_locator').getPos(), text='', text_scale=0.06, textMayChange=True)
        self.nextThemeButton = DirectButton(parent=page, relief=None, geom=(self.gui.find('**/topNext_button/nextButton_up'), self.gui.find('**/topNext_button/nextButton_down'), self.gui.find('**/topNext_button/nextButton_rollover')), command=self.__nextTheme)
        self.prevThemeButton = DirectButton(parent=page, relief=None, geom=(self.gui.find('**/topPrevious_button/previousButton_up'), self.gui.find('**/topPrevious_button/previousButton_down'), self.gui.find('**/topPrevious_button/previousButton_rollover')), command=self.__prevTheme)
        pos = self.gui.find('**/step_06_sendInvitation_locator').getPos()
        self.inviteButton = DirectButton(parent=page, relief=None, geom=(self.gui.find('**/send_up'), self.gui.find('**/send_down'), self.gui.find('**/send_rollover')), text=TTLocalizer.PartyPlannerInviteButton, textMayChange=True, text_scale=0.05, text_pos=(pos[0], pos[2]), command=self.__handleComplete)
        return page

    def __handleHolidays(self):
        self.inviteThemes = range(len(PartyGlobals.InviteTheme))
        if not base.cr.newsManager.isHolidayRunning(ToontownGlobals.VALENTOONS_DAY):
            self.inviteThemes.remove(PartyGlobals.InviteTheme.Valentoons)
        if not base.cr.newsManager.isHolidayRunning(ToontownGlobals.VICTORY_PARTY_HOLIDAY):
            self.inviteThemes.remove(PartyGlobals.InviteTheme.VictoryParty)
        if not base.cr.newsManager.isHolidayRunning(ToontownGlobals.CHRISTMAS):
            self.inviteThemes.remove(PartyGlobals.InviteTheme.Winter)

    def _createFarewellPage(self):
        page = DirectFrame(self.frame)
        page.setName('PartyPlannerFarewellPage')
        self.confirmTitleLabel = DirectLabel(parent=page, relief=None, text=TTLocalizer.PartyPlannerConfirmationAllOkTitle, textMayChange=True, pos=self.gui.find('**/title_locator').getPos(), scale=self.titleScale)
        pos = self.gui.find('**/step_07_close_text_locator').getPos()
        self.closePlannerButton = DirectButton(parent=page, relief=None, geom=(self.gui.find('**/close_up'), self.gui.find('**/close_down'), self.gui.find('**/close_rollover')), text=TTLocalizer.PartyPlannerClosePlanner, text_scale=0.055, text_pos=(pos[0], pos[2]), command=self.__acceptExit)
        return page

    def close(self):
        self.ignore('addPartyResponseReceived')
        self.ignore(localAvatar.uniqueName('moneyChange'))
        self.ignore(localAvatar.uniqueName('bankMoneyChange'))
        self.timeInputHourUpButton.destroy()
        self.timeInputHourDownButton.destroy()
        self.timeInputMinuteUpButton.destroy()
        self.timeInputMinuteDownButton.destroy()
        self.timeInputAmPmUpButton.destroy()
        self.timeInputAmPmDownButton.destroy()
        self.privateButton.destroy()
        self.publicButton.destroy()
        self.makePartyNowButton.destroy()
        self.checkAllButton.destroy()
        self.uncheckAllButton.destroy()
        self.elementBuyButton.destroy()
        self.nextThemeButton.destroy()
        self.prevThemeButton.destroy()
        self.inviteButton.destroy()
        self.closePlannerButton.destroy()
        self.ignore(self.okWithPartyGroundsLayoutEvent)
        if hasattr(self, 'okWithGroundsGui'):
            self.okWithGroundsGui.cleanup()
            del self.okWithGroundsGui
        if hasattr(self, 'frame') and not self.frame.isEmpty():
            messenger.send(self.doneEvent)
            self.hide()
            self.cleanup()
            self.friendList.removeAndDestroyAllItems()
            self.friendList.destroy()
            self.calendarGuiMonth.destroy()
            self.frame.destroy()
        self.partyPlannerHead.delete()
        self.partyPlannerHead.removeNode()
        self.clearNametag()
        self.partyEditor.request('Cleanup')
        self.partyEditor = None
        self.destroy()
        del self
        return

    def __handleComplete(self):
        self.inviteButton['state'] = DirectGuiGlobals.DISABLED
        self.prevButton['state'] = DirectGuiGlobals.DISABLED
        endTime = self.partyTime + self.partyDuration
        hostId = base.localAvatar.doId
        self.partyActivities = self.partyEditor.partyEditorGrid.getActivitiesOnGrid()
        decorations = self.partyEditor.partyEditorGrid.getDecorationsOnGrid()
        invitees = self.getInvitees()
        self.accept('addPartyResponseReceived', self.processAddPartyResponse)
        base.cr.partyManager.sendAddParty(hostId, self.partyTime.strftime('%Y-%m-%d %H:%M:%S'), endTime.strftime('%Y-%m-%d %H:%M:%S'), self.isPrivate, self.currentInvitationTheme, self.partyActivities, decorations, invitees)

    def getInvitees(self):
        invitees = []
        for friendBox in self.friendList['items']:
            if friendBox['indicatorValue']:
                invitees.append(friendBox.getPythonTag('id'))

        return invitees

    def processAddPartyResponse(self, hostId, errorCode):
        PartyPlanner.notify.debug('processAddPartyResponse : hostId=%d errorCode=%s' % (hostId, PartyGlobals.AddPartyErrorCode.getString(errorCode)))
        goingBackAllowed = False
        if errorCode == PartyGlobals.AddPartyErrorCode.AllOk:
            goingBackAllowed = False
            self.confirmTitleLabel['text'] = TTLocalizer.PartyPlannerConfirmationAllOkTitle
            if self.noFriends or len(self.getInvitees()) == 0:
                confirmRecapText = TTLocalizer.PartyPlannerConfirmationAllOkTextNoFriends
            else:
                confirmRecapText = TTLocalizer.PartyPlannerConfirmationAllOkText
        elif errorCode == PartyGlobals.AddPartyErrorCode.ValidationError:
            self.confirmTitleLabel['text'] = TTLocalizer.PartyPlannerConfirmationErrorTitle
            confirmRecapText = TTLocalizer.PartyPlannerConfirmationValidationErrorText
        elif errorCode == PartyGlobals.AddPartyErrorCode.DatabaseError:
            self.confirmTitleLabel['text'] = TTLocalizer.PartyPlannerConfirmationErrorTitle
            confirmRecapText = TTLocalizer.PartyPlannerConfirmationDatabaseErrorText
        elif errorCode == PartyGlobals.AddPartyErrorCode.TooManyHostedParties:
            goingBackAllowed = False
            self.confirmTitleLabel['text'] = TTLocalizer.PartyPlannerConfirmationErrorTitle
            confirmRecapText = TTLocalizer.PartyPlannerConfirmationTooManyText
        self.nametagGroup.setChat(confirmRecapText, CFSpeech)
        self.request('Farewell', goingBackAllowed)

    def __acceptExit(self):
        PartyPlanner.notify.debug('__acceptExit')
        if hasattr(self, 'frame'):
            self.hide()
            messenger.send(self.doneEvent)

    def __nextItem(self):
        messenger.send('wakeup')
        if self.state == 'PartyEditor' and self.okWithGroundsGui.doneStatus != 'ok':
            self.okWithGroundsGui.show()
            return
        if self.state == 'PartyEditor' and self.noFriends:
            self.request('Date')
            self.selectedCalendarGuiDay = None
            self.calendarGuiMonth.clearSelectedDay()
            return
        if self.state == 'Guests':
            self.selectedCalendarGuiDay = None
            self.calendarGuiMonth.clearSelectedDay()
        if self.state == 'Time':
            if self.partyTime < base.cr.toontownTimeManager.getCurServerDateTime():
                self.okChooseFutureTimeEvent = 'okChooseFutureTimeEvent'
                self.acceptOnce(self.okChooseFutureTimeEvent, self.okChooseFutureTime)
                self.chooseFutureTimeDialog = TTDialog.TTGlobalDialog(dialogName=self.uniqueName('chooseFutureTimeDialog'), doneEvent=self.okChooseFutureTimeEvent, message=TTLocalizer.PartyPlannerChooseFutureTime, style=TTDialog.Acknowledge)
                self.chooseFutureTimeDialog.show()
                return
        self.requestNext()
        return

    def okChooseFutureTime(self):
        if hasattr(self, 'chooseFutureTimeDialog'):
            self.chooseFutureTimeDialog.cleanup()
            del self.chooseFutureTimeDialog
        if hasattr(self, 'okChooseFutureTimeEvent'):
            self.ignore(self.okChooseFutureTimeEvent)

    def __prevItem(self):
        messenger.send('wakeup')
        if self.state == 'Date' and self.noFriends:
            self.request('PartyEditor')
            return
        if self.state == 'Invitation' and self.selectedCalendarGuiDay is None:
            self.request('Guests')
            return
        self.requestPrev()
        return

    def __moneyChange(self, newMoney):
        if hasattr(self, 'totalMoney'):
            self.totalMoney = base.localAvatar.getTotalMoney()
        if hasattr(self, 'beanBank'):
            self.beanBank['text'] = str(int(self.totalMoney))
