# types.py
#
# Copyright 2022 James Westman <james@jwestman.net>
#
# This file is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation; either version 3 of the
# License, or (at your option) any later version.
#
# This file 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: LGPL-3.0-or-later


from ..gir import Class, ExternType, Interface, Repository
from .common import *


class TypeName(AstNode):
    grammar = AnyOf(
        [
            Optional(["$", UseLiteral("extern", True)]),
            UseIdent("namespace"),
            ".",
            UseIdent("class_name"),
        ],
        [
            AnyOf("$", [".", UseLiteral("old_extern", True)]),
            UseIdent("class_name"),
            UseLiteral("extern", True),
        ],
        UseIdent("class_name"),
    )

    @property
    def is_extern(self) -> bool:
        return self.tokens["extern"] is True

    @property
    def class_name(self) -> str:
        return self.tokens["class_name"]

    @property
    def namespace(self) -> T.Optional[str]:
        return self.tokens["namespace"]

    @validate()
    def old_extern(self):
        if self.tokens["old_extern"]:
            raise UpgradeWarning(
                "Use the '$' extern syntax introduced in blueprint 0.8.0",
                actions=[CodeAction("Use '$' syntax", "$" + self.tokens["class_name"])],
            )

    @validate("class_name")
    def type_exists(self):
        if not self.is_extern and self.gir_ns is not None:
            self.root.gir.validate_type(self.class_name, self.namespace)

    @validate("namespace")
    def gir_ns_exists(self):
        if not self.is_extern:
            try:
                self.root.gir.validate_ns(self.namespace)
            except CompileError as e:
                ns = self.namespace
                e.actions = [
                    self.root.import_code_action(n, version)
                    for n, version in gir.get_available_namespaces()
                    if n == ns
                ]
                raise e

    @validate()
    def deprecated(self) -> None:
        if self.gir_type and self.gir_type.deprecated:
            hints = []
            if self.gir_type.deprecated_doc:
                hints.append(self.gir_type.deprecated_doc)
            raise DeprecatedWarning(
                f"{self.gir_type.full_name} is deprecated",
                hints=hints,
            )

    @property
    def gir_ns(self) -> T.Optional[gir.Namespace]:
        if not self.is_extern:
            return self.root.gir.namespaces.get(self.namespace or "Gtk")
        return None

    @property
    def gir_type(self) -> T.Optional[gir.GirType]:
        if self.class_name is not None and not self.is_extern:
            return self.root.gir.get_type(self.class_name, self.namespace)

        return gir.ExternType(
            self.root.gir.get_type("Widget", "Gtk").get_containing(Repository),
            self.namespace,
            self.class_name,
        )

    @property
    def glib_type_name(self) -> str:
        if gir_type := self.gir_type:
            return gir_type.glib_type_name
        else:
            if self.namespace:
                return self.namespace + self.class_name
            else:
                return self.class_name

    @docs("namespace")
    def namespace_docs(self):
        if ns := self.root.gir.namespaces.get(self.namespace):
            return ns.doc

    @docs("class_name")
    def class_docs(self):
        if self.gir_type:
            return self.gir_type.doc

    @property
    def as_string(self) -> str:
        name = self.class_name
        if self.namespace:
            name = f"{self.namespace}.{name}"
        if self.is_extern:
            name = f"${name}"
        return name


class ClassName(TypeName):
    @validate("namespace", "class_name")
    def gir_class_exists(self):
        if (
            self.gir_type is not None
            and not isinstance(self.gir_type, ExternType)
            and not isinstance(self.gir_type, Class)
        ):
            if isinstance(self.gir_type, Interface):
                raise CompileError(
                    f"{self.gir_type.full_name} is an interface, not a class"
                )
            else:
                raise CompileError(f"{self.gir_type.full_name} is not a class")


class ConcreteClassName(ClassName):
    @validate("namespace", "class_name")
    def not_abstract(self):
        if isinstance(self.gir_type, Class) and self.gir_type.abstract:
            raise CompileError(
                f"{self.gir_type.full_name} can't be instantiated because it's abstract",
                hints=[f"did you mean to use a subclass of {self.gir_type.full_name}?"],
            )


class TemplateClassName(ClassName):
    """Handles the special case of a template type. The old syntax uses an identifier,
    which is ambiguous with the new syntax. So this class displays an appropriate
    upgrade warning instead of a class not found error."""

    @property
    def is_legacy(self):
        return (
            not self.is_extern
            and self.namespace is None
            and self.root.gir.get_type(self.class_name, "Gtk") is None
        )

    @property
    def gir_type(self) -> T.Optional[gir.GirType]:
        if self.is_legacy:
            return gir.ExternType(self.root.gir, self.namespace, self.class_name)
        else:
            return super().gir_type

    @validate("class_name")
    def type_exists(self):
        if self.is_legacy:
            if type := self.root.gir.get_type_by_cname(self.tokens["class_name"]):
                replacement = type.full_name
            else:
                replacement = "$" + self.tokens["class_name"]

            raise UpgradeWarning(
                "Use type syntax here (introduced in blueprint 0.8.0)",
                actions=[CodeAction("Use type syntax", replace_with=replacement)],
            )

        if not self.is_extern and self.gir_ns is not None:
            self.root.gir.validate_type(self.class_name, self.namespace)
