from PySide6.QtWidgets import (
    QWidget,
    QCalendarWidget,
    QCheckBox,
    QComboBox,
    QDateEdit,
    QGridLayout,
    QGroupBox,
    QLabel,
    QLayout,
    QHBoxLayout,
)
from PySide6.QtGui import QTextCharFormat, QColor, QFont, QBrush
from PySide6.QtCore import QDate, QDateTime, SLOT, Slot, QLocale, Qt
from typing import Optional


class Window(QWidget):
    def __init__(self, parent: Optional[QWidget] = None) -> None:
        super(Window, self).__init__()

        self.createPreviewGroupBox()
        self.createGeneralOptionsGroupBox()
        self.createDatesGroupBox()
        self.createTextFormatsGroupBox()

        layout = QGridLayout()
        layout.addWidget(self.previewGroupBox, 0, 0)
        layout.addWidget(self.generalOptionsGroupBox, 0, 1)
        layout.addWidget(self.datesGroupBox, 1, 0)
        layout.addWidget(self.textFormatsGroupBox, 1, 1)
        layout.setSizeConstraint(QLayout.SetFixedSize)
        self.setLayout(layout)

    def createPreviewGroupBox(self) -> None:
        self.previewGroupBox = QGroupBox("Preview")
        self.calendar = QCalendarWidget()
        self.calendar.setMinimumDate(QDate(1900, 1, 1))
        self.calendar.setMaximumDate(QDate(3000, 1, 1))
        self.calendar.setGridVisible(True)

        self.calendar.currentPageChanged.connect(self.reformatCalendarPage)

        self.previewLayout = QGridLayout()
        self.previewLayout.addWidget(self.calendar, 0, 0, Qt.AlignCenter)
        self.previewGroupBox.setLayout(self.previewLayout)

    def createGeneralOptionsGroupBox(self) -> None:
        self.generalOptionsGroupBox = QGroupBox("General Options")

        self.localeCombo = QComboBox()
        curLocaleIndex = -1
        index = 0

        allLocales = QLocale.matchingLocales(
            QLocale.AnyLanguage, QLocale.AnyScript, QLocale.AnyCountry
        )

        for locale in allLocales:
            lang = locale.language()
            countries = QLocale.countriesForLanguage(lang)
            for country in countries:
                label = QLocale.languageToString(lang)
                label += "/"
                label += QLocale.countryToString(country)
                locale = QLocale(lang, country)
                if (
                    self.locale().language() == lang
                    and self.locale().country() == country
                ):
                    curLocaleIndex = index
                self.localeCombo.addItem(label, locale)
                index += 1

        if curLocaleIndex != -1:
            self.localeCombo.setCurrentIndex(curLocaleIndex)
        self.localeLabel = QLabel("&Locale")
        self.localeLabel.setBuddy(self.localeCombo)

        self.firstDayCombo = QComboBox()
        self.firstDayCombo.addItem("Sunday", Qt.Sunday)
        self.firstDayCombo.addItem("Monday", Qt.Monday)
        self.firstDayCombo.addItem("Tuesday", Qt.Tuesday)
        self.firstDayCombo.addItem("Wednesday", Qt.Wednesday)
        self.firstDayCombo.addItem("Thursday", Qt.Thursday)
        self.firstDayCombo.addItem("Friday", Qt.Friday)
        self.firstDayCombo.addItem("Saturday", Qt.Saturday)

        self.firstDayLabel = QLabel("Wee&k starts on:")
        self.firstDayLabel.setBuddy(self.firstDayCombo)

        self.selectionModeCombo = QComboBox()
        self.selectionModeCombo.addItem(
            "Single selection", QCalendarWidget.SingleSelection
        )
        self.selectionModeCombo.addItem("None", QCalendarWidget.NoSelection)

        self.selectionModeLabel = QLabel("&Selection mode:")
        self.selectionModeLabel.setBuddy(self.selectionModeCombo)

        self.gridCheckBox = QCheckBox("&Grid")
        self.gridCheckBox.setChecked(self.calendar.isGridVisible())

        self.navigationCheckBox = QCheckBox("&Navigation bar")
        self.navigationCheckBox.setChecked(True)

        self.horizontalHeaderCombo = QComboBox()
        self.horizontalHeaderCombo.addItem(
            "Single letter day names", QCalendarWidget.SingleLetterDayNames
        )
        self.horizontalHeaderCombo.addItem(
            "Short day names", QCalendarWidget.ShortDayNames
        )
        self.horizontalHeaderCombo.addItem("None", QCalendarWidget.NoHorizontalHeader)
        self.horizontalHeaderCombo.setCurrentIndex(1)

        self.horizontalHeaderLabel = QLabel("&Horizontal header:")
        self.horizontalHeaderLabel.setBuddy(self.horizontalHeaderCombo)

        self.verticalHeaderCombo = QComboBox()
        self.verticalHeaderCombo.addItem(
            "ISO week numbers", QCalendarWidget.ISOWeekNumbers
        )
        self.verticalHeaderCombo.addItem("None", QCalendarWidget.NoVerticalHeader)

        self.verticalHeaderLabel = QLabel("&Vertical header:")
        self.verticalHeaderLabel.setBuddy(self.verticalHeaderCombo)

        self.localeCombo.currentIndexChanged[int].connect(self.localeChanged)
        self.firstDayCombo.currentIndexChanged[int].connect(self.firstDayChanged)
        self.selectionModeCombo.currentIndexChanged[int].connect(
            self.selectionModeChanged
        )
        self.gridCheckBox.toggled.connect(self.calendar.setGridVisible)
        self.navigationCheckBox.toggled.connect(self.calendar.setNavigationBarVisible)
        self.horizontalHeaderCombo.currentIndexChanged[int].connect(
            self.horizontalHeaderChanged
        )
        self.verticalHeaderCombo.currentIndexChanged[int].connect(
            self.verticalHeaderChanged
        )

        checkBoxLayout = QHBoxLayout()
        checkBoxLayout.addWidget(self.gridCheckBox)
        checkBoxLayout.addStretch()
        checkBoxLayout.addWidget(self.navigationCheckBox)

        outerLayout = QGridLayout()
        outerLayout.addWidget(self.localeLabel, 0, 0)
        outerLayout.addWidget(self.localeCombo, 0, 1)
        outerLayout.addWidget(self.firstDayLabel, 1, 0)
        outerLayout.addWidget(self.firstDayCombo, 1, 1)
        outerLayout.addWidget(self.selectionModeLabel, 2, 0)
        outerLayout.addWidget(self.selectionModeCombo, 2, 1)
        outerLayout.addLayout(checkBoxLayout, 3, 0, 1, 2)
        outerLayout.addWidget(self.horizontalHeaderLabel, 4, 0)
        outerLayout.addWidget(self.horizontalHeaderCombo, 4, 1)
        outerLayout.addWidget(self.verticalHeaderLabel, 5, 0)
        outerLayout.addWidget(self.verticalHeaderCombo, 5, 1)
        self.generalOptionsGroupBox.setLayout(outerLayout)

        self.firstDayChanged(self.firstDayCombo.currentIndex())
        self.selectionModeChanged(self.selectionModeCombo.currentIndex())
        self.horizontalHeaderChanged(self.horizontalHeaderCombo.currentIndex())
        self.verticalHeaderChanged(self.verticalHeaderCombo.currentIndex())

    def createDatesGroupBox(self) -> None:
        self.datesGroupBox = QGroupBox("Dates")

        self.minimumDateEdit = QDateEdit()
        self.minimumDateEdit.setDisplayFormat("MMM d yyyy")
        self.minimumDateEdit.setDateRange(
            self.calendar.minimumDate(), self.calendar.maximumDate()
        )
        self.minimumDateEdit.setDate(self.calendar.minimumDate())

        self.minimumDateLabel = QLabel("&Minimum Date:")
        self.minimumDateLabel.setBuddy(self.minimumDateEdit)

        self.currentDateEdit = QDateEdit()
        self.currentDateEdit.setDisplayFormat("MMM d yyyy")
        self.currentDateEdit.setDate(self.calendar.selectedDate())
        self.currentDateEdit.setDateRange(
            self.calendar.minimumDate(), self.calendar.maximumDate()
        )

        self.currentDateLabel = QLabel("&Current Date:")
        self.currentDateLabel.setBuddy(self.currentDateEdit)

        self.maximumDateEdit = QDateEdit()
        self.maximumDateEdit.setDisplayFormat("MMM d yyyy")
        self.maximumDateEdit.setDateRange(
            self.calendar.minimumDate(), self.calendar.maximumDate()
        )
        self.maximumDateEdit.setDate(self.calendar.maximumDate())

        self.maximumDateLabel = QLabel("Ma&ximum Date:")
        self.maximumDateLabel.setBuddy(self.maximumDateEdit)

        self.currentDateEdit.dateChanged.connect(self.calendar.setSelectedDate)
        self.calendar.selectionChanged.connect(self.selectedDateChanged)
        self.minimumDateEdit.dateChanged[QDate].connect(self.minimumDateChanged)
        self.maximumDateEdit.dateChanged[QDate].connect(self.maximumDateChanged)

        dateBoxLayout = QGridLayout()
        dateBoxLayout.addWidget(self.currentDateLabel, 1, 0)
        dateBoxLayout.addWidget(self.currentDateEdit, 1, 1)
        dateBoxLayout.addWidget(self.minimumDateLabel, 0, 0)
        dateBoxLayout.addWidget(self.minimumDateEdit, 0, 1)
        dateBoxLayout.addWidget(self.maximumDateLabel, 2, 0)
        dateBoxLayout.addWidget(self.maximumDateEdit, 2, 1)
        dateBoxLayout.setRowStretch(3, 1)

        self.datesGroupBox.setLayout(dateBoxLayout)

    def createTextFormatsGroupBox(self) -> None:
        self.textFormatsGroupBox = QGroupBox("Text Formats")

        self.weekdayColorCombo = self.createColorComboBox()
        self.weekdayColorCombo.setCurrentIndex(self.weekdayColorCombo.findText("Black"))

        self.weekdayColorLabel = QLabel("&Weekday color:")
        self.weekdayColorLabel.setBuddy(self.weekdayColorCombo)

        self.weekendColorCombo = self.createColorComboBox()
        self.weekendColorCombo.setCurrentIndex(self.weekdayColorCombo.findText("Red"))

        self.weekendColorLabel = QLabel("Week&end color:")
        self.weekendColorLabel.setBuddy(self.weekendColorCombo)

        self.headerTextFormatCombo = QComboBox()
        self.headerTextFormatCombo.addItem("Bold")
        self.headerTextFormatCombo.addItem("Italic")
        self.headerTextFormatCombo.addItem("Plain")

        self.headerTextFormatLabel = QLabel("&Header text:")
        self.headerTextFormatLabel.setBuddy(self.headerTextFormatCombo)

        self.firstFridayCheckBox = QCheckBox("&First Friday in blue")
        self.mayFirstCheckBox = QCheckBox("May &1 in red")

        self.weekdayColorCombo.currentIndexChanged[int].connect(
            self.weekdayFormatChanged
        )
        self.weekdayColorCombo.currentIndexChanged[int].connect(
            self.reformatCalendarPage
        )
        self.weekendColorCombo.currentIndexChanged[int].connect(
            self.weekendFormatChanged
        )
        self.weekendColorCombo.currentIndexChanged[int].connect(
            self.reformatCalendarPage
        )
        self.headerTextFormatCombo.currentIndexChanged[int].connect(
            self.reformatHeaders
        )
        self.firstFridayCheckBox.toggled.connect(self.reformatCalendarPage)
        self.mayFirstCheckBox.toggled.connect(self.reformatCalendarPage)

        checkBoxLayout = QHBoxLayout()
        checkBoxLayout.addWidget(self.firstFridayCheckBox)
        checkBoxLayout.addStretch()
        checkBoxLayout.addWidget(self.mayFirstCheckBox)

        outerLayout = QGridLayout()
        outerLayout.addWidget(self.weekdayColorLabel, 0, 0)
        outerLayout.addWidget(self.weekdayColorCombo, 0, 1)
        outerLayout.addWidget(self.weekendColorLabel, 1, 0)
        outerLayout.addWidget(self.weekendColorCombo, 1, 1)
        outerLayout.addWidget(self.headerTextFormatLabel, 2, 0)
        outerLayout.addWidget(self.headerTextFormatCombo, 2, 1)
        outerLayout.addLayout(checkBoxLayout, 3, 0, 1, 2)
        self.textFormatsGroupBox.setLayout(outerLayout)

        self.weekdayFormatChanged()
        self.weekendFormatChanged()
        self.reformatHeaders()
        self.reformatCalendarPage()

    def createColorComboBox(self) -> QComboBox:
        comboBox = QComboBox()
        comboBox.addItem("Red", QColor(Qt.red))
        comboBox.addItem("Blue", QColor(Qt.blue))
        comboBox.addItem("Black", QColor(Qt.black))
        comboBox.addItem("Magenta", QColor(Qt.magenta))
        return comboBox

    @Slot(int)
    def localeChanged(self, index: int) -> None:
        newLocale = QLocale(self.localeCombo.itemData(index))
        self.calendar.setLocale(newLocale)
        newLocaleFirstDayIndex = self.firstDayCombo.findData(newLocale.firstDayOfWeek())
        self.firstDayCombo.setCurrentIndex(newLocaleFirstDayIndex)

    @Slot(int) 
    def firstDayChanged(self, index: int) -> None:
        self.calendar.setFirstDayOfWeek(
            Qt.DayOfWeek(self.firstDayCombo.itemData(index))
        )

    @Slot(int) 
    def selectionModeChanged(self, index: int) -> None:
        self.calendar.setSelectionMode(
            QCalendarWidget.SelectionMode(self.selectionModeCombo.itemData(index))
        )

    @Slot(int) 
    def horizontalHeaderChanged(self, index: int) -> None:
        self.calendar.setHorizontalHeaderFormat(
            QCalendarWidget.HorizontalHeaderFormat(
                self.horizontalHeaderCombo.itemData(index)
            )
        )

    @Slot(int) 
    def verticalHeaderChanged(self, index: int) -> None:
        self.calendar.setVerticalHeaderFormat(
            QCalendarWidget.VerticalHeaderFormat(
                self.verticalHeaderCombo.itemData(index)
            )
        )

    @Slot() 
    def selectedDateChanged(self) -> None:
        self.currentDateEdit.setDate(self.calendar.selectedDate())

    @Slot(QDate) 
    def minimumDateChanged(self, date: QDate) -> None:
        self.calendar.setMinimumDate(date)
        self.maximumDateEdit.setDate(self.calendar.maximumDate())

    @Slot(QDate) 
    def maximumDateChanged(self, date: QDate) -> None:
        self.calendar.setMaximumDate(date)
        self.maximumDateEdit.setDate(self.calendar.minimumDate())

    @Slot() 
    def weekdayFormatChanged(self) -> None:
        textCharFormat = QTextCharFormat()

        textCharFormat.setForeground(
            self.weekdayColorCombo.itemData(self.weekdayColorCombo.currentIndex())
        )

        self.calendar.setWeekdayTextFormat(Qt.Monday, textCharFormat)
        self.calendar.setWeekdayTextFormat(Qt.Tuesday, textCharFormat)
        self.calendar.setWeekdayTextFormat(Qt.Wednesday, textCharFormat)
        self.calendar.setWeekdayTextFormat(Qt.Thursday, textCharFormat)
        self.calendar.setWeekdayTextFormat(Qt.Friday, textCharFormat)

    @Slot() 
    def weekendFormatChanged(self) -> None:
        textCharFormat = QTextCharFormat()

        textCharFormat.setForeground(
            self.weekendColorCombo.itemData(self.weekendColorCombo.currentIndex())
        )

        self.calendar.setWeekdayTextFormat(Qt.Saturday, textCharFormat)
        self.calendar.setWeekdayTextFormat(Qt.Sunday, textCharFormat)

    @Slot() 
    def reformatHeaders(self) -> None:
        text = self.headerTextFormatCombo.currentText()
        textCharFormat = QTextCharFormat()

        if text == "Bold":
            textCharFormat.setFontWeight(QFont.Bold)
        elif text == "Italic":
            textCharFormat.setFontItalic(True)
        elif text == "Green":
            textCharFormat.setForeground(Qt.green)
        self.calendar.setHeaderTextFormat(textCharFormat)

    @Slot() 
    def reformatCalendarPage(self) -> None:
        mayFirstFormat = QTextCharFormat()
        mayFirst = QDate(self.calendar.yearShown(), 5, 1)

        firstFridayFormat = QTextCharFormat()
        firstFriday = QDate(self.calendar.yearShown(), self.calendar.monthShown(), 1)
        while firstFriday.dayOfWeek() != Qt.Friday:
            firstFriday = firstFriday.addDays(1)

        if self.firstFridayCheckBox.isChecked():
            firstFridayFormat.setForeground(Qt.blue)
        else:
            dayOfWeek = Qt.DayOfWeek(firstFriday.dayOfWeek())
            firstFridayFormat.setForeground(
                self.calendar.weekdayTextFormat(dayOfWeek).foreground()
            )

        self.calendar.setDateTextFormat(firstFriday, firstFridayFormat)

        # When it is checked, "May first in Red" takes precedence over "First Friday in blue"
        if self.mayFirstCheckBox.isChecked():
            mayFirstFormat.setForeground(QBrush(Qt.red))
        elif not self.firstFridayCheckBox.isChecked() or firstFriday != mayFirst:
            dayOfWeek = Qt.DayOfWeek(mayFirst.dayOfWeek())
            self.calendar.setDateTextFormat(
                mayFirst, self.calendar.weekdayTextFormat(dayOfWeek)
            )

        self.calendar.setDateTextFormat(mayFirst, mayFirstFormat)
