"""
novelWriter – Project Data Class
================================

File History:
Created: 2022-10-30 [2.0rc2] NWProjectData

This file is a part of novelWriter
Copyright (C) 2022 Veronica Berglyd Olsen and novelWriter contributors

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 3 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, see <https://www.gnu.org/licenses/>.
"""  # noqa

from __future__ import annotations

import logging
import uuid

from typing import TYPE_CHECKING, Any, Literal

from novelwriter.common import checkBool, checkInt, checkStringNone, checkUuid, isHandle, makeFileNameSafe, simplified
from novelwriter.core.status import NWStatus

if TYPE_CHECKING:
    from novelwriter.core.project import NWProject

logger = logging.getLogger(__name__)

T_LastHandle = Literal["editor", "viewer", "novel", "outline"]


class NWProjectData:
    """Core: Project Data Class.

    The class holds all project data from the main XML file, aside from
    the list of project items.
    """

    def __init__(self, project: NWProject) -> None:

        self._project = project

        # Project Meta
        self._uuid = ""
        self._name = ""
        self._author = ""
        self._saveCount = 0
        self._autoCount = 0
        self._editTime = 0

        # Project Settings
        self._doBackup = True
        self._language = None
        self._spellCheck = False
        self._spellLang = None

        # Project Dictionaries
        self._initCounts = [0, 0, 0, 0]
        self._currCounts = [0, 0, 0, 0]
        self._lastHandle: dict[str, str | None] = {
            "editor": None,
            "viewer": None,
            "novel": None,
            "outline": None,
        }
        self._autoReplace: dict[str, str] = {}
        self._titleFormat: dict[str, str] = {
            "title": "%title%",
            "chapter": "%title%",
            "unnumbered": "%title%",
            "scene": "* * *",
            "section": "",
        }

        self._status = NWStatus(NWStatus.STATUS)
        self._import = NWStatus(NWStatus.IMPORT)

    ##
    #  Properties
    ##

    @property
    def uuid(self) -> str:
        """Return the project ID."""
        return self._uuid

    @property
    def name(self) -> str:
        """Return the project name."""
        return self._name

    @property
    def fileSafeName(self) -> str:
        """Return the project name in a file name safe format."""
        return makeFileNameSafe(self._name)

    @property
    def author(self) -> str:
        """Return the project author."""
        return self._author

    @property
    def saveCount(self) -> int:
        """Return the count of project saves."""
        return self._saveCount

    @property
    def autoCount(self) -> int:
        """Return the count of project auto-saves."""
        return self._autoCount

    @property
    def editTime(self) -> int:
        """Return the number of seconds the project has been edited."""
        return self._editTime

    @property
    def doBackup(self) -> bool:
        """Return the backup setting."""
        return self._doBackup

    @property
    def language(self) -> str | None:
        """Return the project language setting."""
        return self._language

    @property
    def spellCheck(self) -> bool:
        """Return the spell check enabled setting."""
        return self._spellCheck

    @property
    def spellLang(self) -> str | None:
        """Return the spell check language."""
        return self._spellLang

    @property
    def initCounts(self) -> tuple[int, int, int, int]:
        """Return the initial count of words and characters for novel
        and note documents.
        """
        return self._initCounts[0], self._initCounts[1], self._initCounts[2], self._initCounts[3]

    @property
    def currCounts(self) -> tuple[int, int, int, int]:
        """Return the current count of words and characters for novel
        and note documents.
        """
        return self._currCounts[0], self._currCounts[1], self._currCounts[2], self._currCounts[3]

    @property
    def lastHandle(self) -> dict[str, str | None]:
        """Return the dictionary of last used handles for various
        components of the GUI.
        """
        return self._lastHandle

    @property
    def autoReplace(self) -> dict[str, str]:
        """Return the auto-replace dictionary."""
        return self._autoReplace

    @property
    def itemStatus(self) -> NWStatus:
        """Return the status settings object."""
        return self._status

    @property
    def itemImport(self) -> NWStatus:
        """Return the importance settings object."""
        return self._import

    ##
    #  Methods
    ##

    def incSaveCount(self) -> None:
        """Increment the save count by one."""
        self._saveCount += 1
        self._project.setProjectChanged(True)

    def incAutoCount(self) -> None:
        """Increment the auto save count by one."""
        self._autoCount += 1
        self._project.setProjectChanged(True)

    ##
    #  Getters
    ##

    def getLastHandle(self, component: str) -> str | None:
        """Retrieve the last used handle for a given component."""
        return self._lastHandle.get(component, None)

    ##
    #  Setters
    ##

    def setUuid(self, value: Any) -> None:
        """Set the project id."""
        value = checkUuid(value, "")
        if not value:
            self._uuid = str(uuid.uuid4())
        elif value != self._uuid:
            self._uuid = value
            self._project.setProjectChanged(True)

    def setName(self, value: str | None) -> None:
        """Set a new project name."""
        if value != self._name:
            self._name = simplified(str(value or ""))
            self._project.setProjectChanged(True)

    def setAuthor(self, value: str | None) -> None:
        """Set the author value."""
        if value != self._author:
            self._author = simplified(str(value or ""))
            self._project.setProjectChanged(True)

    def setSaveCount(self, value: Any) -> None:
        """Set the save count from last session."""
        self._saveCount = checkInt(value, 0)
        self._project.setProjectChanged(True)

    def setAutoCount(self, value: Any) -> None:
        """Set the auto save count from last session."""
        self._autoCount = checkInt(value, 0)
        self._project.setProjectChanged(True)

    def setEditTime(self, value: Any) -> None:
        """Set the edit time from last session."""
        self._editTime = checkInt(value, 0)
        self._project.setProjectChanged(True)

    def setDoBackup(self, value: Any) -> None:
        """Set the do write backup flag."""
        if value != self._doBackup:
            self._doBackup = checkBool(value, False)
            self._project.setProjectChanged(True)

    def setLanguage(self, value: str | None) -> None:
        """Set the project language."""
        if value != self._language:
            self._language = checkStringNone(value, None)
            self._project.setProjectChanged(True)

    def setSpellCheck(self, value: Any) -> None:
        """Set the spell check flag."""
        if value != self._spellCheck:
            self._spellCheck = checkBool(value, False)
            self._project.setProjectChanged(True)

    def setSpellLang(self, value: str | None) -> None:
        """Set the spell check language."""
        if value != self._spellLang:
            self._spellLang = checkStringNone(value, None)
            self._project.setProjectChanged(True)

    def setLastHandle(self, value: str | None, component: T_LastHandle) -> None:
        """Set a last used handle into the handle registry for a given
        component.
        """
        if isinstance(component, str):
            self._lastHandle[component] = checkStringNone(value, None)
            self._project.setProjectChanged(True)

    def setLastHandles(self, value: dict) -> None:
        """Set the full last handles dictionary to a new set of values.
        This is intended to be used at project load.
        """
        if isinstance(value, dict):
            for key, entry in value.items():
                if key in self._lastHandle:
                    self._lastHandle[key] = str(entry) if isHandle(entry) else None
            self._project.setProjectChanged(True)

    def setInitCounts(self, wNovel: Any = None, wNotes: Any = None, cNovel: Any = None, cNotes: Any = None) -> None:
        """Set the count totals for novel and note files."""
        if wNovel is not None:
            count = checkInt(wNovel, 0)
            self._initCounts[0] = count
            self._currCounts[0] = count
        if wNotes is not None:
            count = checkInt(wNotes, 0)
            self._initCounts[1] = count
            self._currCounts[1] = count
        if cNovel is not None:
            count = checkInt(cNovel, 0)
            self._initCounts[2] = count
            self._currCounts[2] = count
        if cNotes is not None:
            count = checkInt(cNotes, 0)
            self._initCounts[3] = count
            self._currCounts[3] = count

    def setCurrCounts(self, wNovel: Any = None, wNotes: Any = None, cNovel: Any = None, cNotes: Any = None) -> None:
        """Set the count totals for novel and note files."""
        if wNovel is not None:
            self._currCounts[0] = checkInt(wNovel, 0)
        if wNotes is not None:
            self._currCounts[1] = checkInt(wNotes, 0)
        if cNovel is not None:
            self._currCounts[2] = checkInt(cNovel, 0)
        if cNotes is not None:
            self._currCounts[3] = checkInt(cNotes, 0)

    def setAutoReplace(self, value: dict) -> None:
        """Set the auto-replace dictionary."""
        if isinstance(value, dict):
            self._autoReplace = {}
            for key, entry in value.items():
                if isinstance(entry, str):
                    self._autoReplace[key] = simplified(entry)
            self._project.setProjectChanged(True)
