# values.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 .common import *


class Value(AstNode):
    pass


class TranslatedStringValue(Value):
    grammar = AnyOf(
        [
            "_",
            "(",
            UseQuoted("value").expected("a quoted string"),
            Match(")").expected(),
        ],
        [
            "C_",
            "(",
            UseQuoted("context").expected("a quoted string"),
            ",",
            UseQuoted("value").expected("a quoted string"),
            Optional(","),
            Match(")").expected(),
        ],
    )

    @property
    def attrs(self):
        attrs = { "translatable": "true" }
        if "context" in self.tokens:
            attrs["context"] = self.tokens["context"]
        return attrs

    def emit_xml(self, xml: XmlEmitter):
        xml.put_text(self.tokens["value"])


class LiteralValue(Value):
    grammar = AnyOf(
        UseNumber("value"),
        UseQuoted("value"),
    )

    def emit_xml(self, xml: XmlEmitter):
        xml.put_text(self.tokens["value"])

    @validate()
    def validate_for_type(self):
        type = self.parent.value_type
        if isinstance(type, gir.IntType):
            try:
                int(self.tokens["value"])
            except:
                raise CompileError(f"Cannot convert {self.group.tokens['value']} to integer")

        elif isinstance(type, gir.UIntType):
            try:
                int(self.tokens["value"])
                if int(self.tokens["value"]) < 0:
                    raise Exception()
            except:
                raise CompileError(f"Cannot convert {self.group.tokens['value']} to unsigned integer")

        elif isinstance(type, gir.FloatType):
            try:
                float(self.tokens["value"])
            except:
                raise CompileError(f"Cannot convert {self.group.tokens['value']} to float")

        elif isinstance(type, gir.StringType):
            pass

        elif isinstance(type, gir.Class) or isinstance(type, gir.Interface):
            parseable_types = [
                "Gdk.Paintable",
                "Gdk.Texture",
                "Gdk.Pixbuf",
                "GLib.File",
                "Gtk.ShortcutTrigger",
                "Gtk.ShortcutAction",
            ]
            if type.full_name not in parseable_types:
                raise CompileError(f"Cannot convert {self.group.tokens['value']} to {type.full_name}")

        elif type is not None:
            raise CompileError(f"Cannot convert {self.group.tokens['value']} to {type.full_name}")


class Flag(AstNode):
    grammar = UseIdent("value")

    @docs()
    def docs(self):
        type = self.parent.parent.value_type
        if not isinstance(type, Enumeration):
            return
        if member := type.members.get(self.tokens["value"]):
            return member.doc

    @validate()
    def validate_for_type(self):
        type = self.parent.parent.value_type
        if isinstance(type, gir.Bitfield) and self.tokens["value"] not in type.members:
            raise CompileError(
                f"{self.tokens['value']} is not a member of {type.full_name}",
                did_you_mean=(self.tokens['value'], type.members.keys()),
            )


class FlagsValue(Value):
    grammar = [Flag, "|", Delimited(Flag, "|")]

    @validate()
    def parent_is_bitfield(self):
        type = self.parent.value_type
        if type is not None and not isinstance(type, gir.Bitfield):
            raise CompileError(f"{type.full_name} is not a bitfield type")

    def emit_xml(self, xml: XmlEmitter):
        xml.put_text("|".join([flag.tokens["value"] for flag in self.children[Flag]]))


class IdentValue(Value):
    grammar = UseIdent("value")

    def emit_xml(self, xml: XmlEmitter):
        if isinstance(self.parent.value_type, gir.Enumeration):
            xml.put_text(self.parent.value_type.members[self.tokens["value"]].nick)
        else:
            xml.put_text(self.tokens["value"])

    @validate()
    def validate_for_type(self):
        type = self.parent.value_type

        if isinstance(type, gir.Enumeration):
            if self.tokens["value"] not in type.members:
                raise CompileError(
                    f"{self.tokens['value']} is not a member of {type.full_name}",
                    did_you_mean=(self.tokens['value'], type.members.keys()),
                )

        elif isinstance(type, gir.BoolType):
            if self.tokens["value"] not in ["true", "false"]:
                raise CompileError(
                    f"Expected 'true' or 'false' for boolean value",
                    did_you_mean=(self.tokens['value'], ["true", "false"]),
                )

        elif type is not None:
            object = self.root.objects_by_id.get(self.tokens["value"])
            if object is None:
                raise CompileError(
                    f"Could not find object with ID {self.tokens['value']}",
                    did_you_mean=(self.tokens['value'], self.root.objects_by_id.keys()),
                )
            elif object.gir_class and not object.gir_class.assignable_to(type):
                raise CompileError(
                    f"Cannot assign {object.gir_class.full_name} to {type.full_name}"
                )


    @docs()
    def docs(self):
        type = self.parent.value_type
        if isinstance(type, gir.Enumeration):
            if member := type.members.get(self.tokens["value"]):
                return member.doc
            else:
                return type.doc
        elif isinstance(type, gir.GirNode):
            return type.doc


    def get_semantic_tokens(self) -> T.Iterator[SemanticToken]:
        if isinstance(self.parent.value_type, gir.Enumeration):
            token = self.group.tokens["value"]
            yield SemanticToken(token.start, token.end, SemanticTokenType.EnumMember)

