"""This modules contains the JSON Class types marker."""
from __future__ import annotations
from typing import Callable, Any, Optional, Union, Literal
from copy import deepcopy
from .fdef import Fdef
from .modifiers import (UseForModifier, BoolModifier, ChainedModifier,
                        CompareModifier, DateModifier, DatetimeModifier,
                        DefaultModifier, DictOfModifier, EagerModifier,
                        EmbeddedModifier, EnumModifier,
                        FloatModifier, IndexModifier, InputAllModifier,
                        InputNameModifier, InputLnameModifier,
                        InputValueModifier, InstanceOfModifier,
                        IntModifier, InvalidModifier, LengthModifier,
                        LinkedByModifier, LinkedInModifier,
                        LinkedThruModifier, LinkToModifier, ListOfModifier,
                        MatchModifier, MaxModifier, MaxlengthModifier,
                        MinModifier, MinlengthModifier, NegativeModifier,
                        NonnullModifier, NullableModifier, OneOfModifier,
                        UnionModifier, OnWriteModifier, OnSaveModifier,
                        OnUpdateModifier, OutputLnameModifier,
                        OutputNameModifier, OutputValueModifier,
                        PositiveModifier, PresentModifier,
                        PresentWithModifier, PresentWithoutModifier,
                        PreserializeModifier, PrimaryModifier,
                        RangeModifier, ReadonlyModifier, ReadwriteModifier,
                        RefereeModifier, ReferrerModifier,
                        RequiredModifier, ResetModifier, SetOnSaveModifier,
                        ShapeModifier, StrModifier, StrictModifier,
                        TempModifier, TransformModifier, TrimModifier,
                        TruncateModifier, UniqueModifier, ValidateModifier,
                        Modifier, WriteNonnullModifier, WriteonceModifier,
                        WriteonlyModifier, DenyModifier, CascadeModifier,
                        NullifyModifier, OpModifier, AsopModifier,
                        AsopdModifier, UrlModifier, EmailModifier,
                        DigitModifier,AlphaModifier,NumericModifier,
                        AlnumModifier,ToTitleModifier,ToCapModifier,
                        ToLowerModifier,ToUpperModifier,
                        UnresolvedModifier, AnyModifier)

Str = str
Int = int
Float = float


class Types:
    """The class of types marks object. Types marks provide necessary information
    about an json object's shape, transformation, validation, serialization and
    sanitization.
    """

    def __init__(  # pylint: disable=keyword-arg-before-vararg
        self,
        original: Optional[Types] = None,
        *args: Modifier
    ) -> None:
        if not original:
            self.fdef = Fdef()
            self.modifier = ChainedModifier()
        else:
            self.fdef = deepcopy(original.fdef)
            modifier = original.modifier
            for arg in args:
                modifier = modifier.append(arg)
                arg.define(self.fdef)
            self.modifier = modifier

    @property
    def invalid(self) -> Types:
        """Fields marked with invalid will never be valid, thus these fields
        will never pass validation.
        """
        return Types(self, InvalidModifier())

    @property
    def primary(self) -> Types:
        """Field marked with primary become the object's primary key.
        """
        return Types(self, PrimaryModifier())

    def usefor(self, usage: str) -> Types:
        """Field marked with usefor are queried by JSON Class and it's ORM
        implementations to get user designated fields to perform special
        actions.
        """
        return Types(self, UseForModifier(usage))

    def timestamp(self,
                  usage: Literal['created', 'updated', 'deleted']) -> Types:
        """Field marked with timestamp are special timestamp marks. JSON Class
        and it's ORM implementations use this information to perform special
        actions on timestamp fields.
        """
        return Types(self, UseForModifier(f'{usage}_at'))

    @property
    def readonly(self) -> Types:
        """Fields marked with readonly will not be able to go through
        initialization and set method. You can update value of these fields
        directly or through update method. This prevents client side to post
        data directly into these fields.

        `writeonce`, `readonly` and `writenonnull` cannot be presented
        together.
        """
        return Types(self, ReadonlyModifier())

    @property
    def writeonly(self) -> Types:
        """Fields marked with writeonly will not be available in outgoing json
        form. Users' password is a great example of writeonly.
        """
        return Types(self, WriteonlyModifier())

    @property
    def readwrite(self) -> Types:
        """Fields marked with readwrite will be presented in both inputs and
        outputs. This is the default behavior. And this specifier can be
        omitted.
        """
        return Types(self, ReadwriteModifier())

    @property
    def writeonce(self) -> Types:
        """Fields marked with writeonce can only be set once through
        initialization and set method. You can update value of these fields
        directly or through update method. This is suitable for e.g. dating app
        user gender. Gender should not be changed once set.

        `writeonce`, `readonly` and `writenonnull` cannot be presented
        together.
        """
        return Types(self, WriteonceModifier())

    @property
    def writenonnull(self) -> Types:
        """Fields marked with writenonnull can only be set to a nonnull value.
        The update method doesn't have this limitation. This prevents user from
        setting present value back into None.

        `writeonce`, `readonly` and `writenonnull` cannot be presented
        together.
        """
        return Types(self, WriteNonnullModifier())

    @property
    def internal(self) -> Types:
        """Fields marked with internal will not be accepted as input, and it
        will not be present in output. These fields are internal and hidden
        from users.
        """
        return Types(self, ReadonlyModifier(), WriteonlyModifier())

    @property
    def temp(self) -> Types:
        """Fields marked with temp won't be written into database. As soon as
        database writing happens, value of this field is cleared and set to
        None. Examples of it's use cases are authentication code validation,
        input validation, etc.
        """
        return Types(self, TempModifier())

    @property
    def index(self) -> Types:
        """Fields marked with index are picked up by ORM integrations to setup
        database column index for you. This marker doesn't have any effect
        around transforming and validating.
        """
        return Types(self, IndexModifier())

    @property
    def unique(self) -> Types:
        """Fields marked with unique are picked up by ORM integrations to setup
        database column unique index for you. This marker doesn't have any
        effect around transforming and validating. When database engine raises
        an exception, jsonclasses's web framework integration will catch it and
        return 400 automatically.

        If you are implementing jsonclasses ORM integration, you should use
        UniqueFieldException provided by jsonclasses.excs to keep
        consistency with other jsonclasses integrations.
        """
        return Types(self, UniqueModifier())

    @property
    def embedded(self) -> Types:
        """Instance fields marked with the embedded mark is embedded into the
        hosting document for noSQL databases.
        """
        return Types(self, EmbeddedModifier())

    @property
    def linkto(self) -> Types:
        """In a database relationship, fields marked with linkto save an id of
        the object being referenced at the local table.
        """
        return Types(self, LinkToModifier())

    def linkedby(self, foreign_key: str) -> Types:
        """In a database relationship, fields marked with linkedby find
        reference from the destination table.
        """
        return Types(self, LinkedByModifier(foreign_key))

    def linkedthru(self, foreign_key: str) -> Types:
        """In a database relationship, fields marked with linkedthru save
        relationships to a designated association table and find references
        through it.
        """
        return Types(self, LinkedThruModifier(foreign_key))

    def linkedin(self, cls: Any) -> Types:
        """In a database relationship, fields marked with linkedin save
        relationships to the table under provided class.
        """
        return Types(self, LinkedInModifier(cls))

    def referrer(self, referrer_key: str) -> Types:
        """In a many to many database relationship, fields marked with referrer
        has a provided custom key name in the association table.
        """
        return Types(self, ReferrerModifier(referrer_key))

    def referee(self, referee_key: str) -> Types:
        """In a many to many database relationship, fields marked with referee
        reference the other side of the relationship with this provided custom
        key name.
        """
        return Types(self, RefereeModifier(referee_key))

    @property
    def nullify(self) -> Types:
        """When an object is deleted, linked objects' references are set to
        null instead of deleted.
        """
        return Types(self, NullifyModifier())

    @property
    def cascade(self) -> Types:
        """When an object is deleted, linked objects with cascade relationship
        are deleted.
        """
        return Types(self, CascadeModifier())

    @property
    def deny(self) -> Types:
        """When an object is deleted, linked objects with deny relationship
        prevent this object being deleted.
        """
        return Types(self, DenyModifier())

    @property
    def str(self) -> Types:
        """Fields marked with str should be str type. This is a type marker.
        """
        return Types(self, StrModifier())

    def match(self, pattern: Str) -> Types:
        """Fields marked with match are tested againest the argument regular
        expression pattern.
        """
        return Types(self, MatchModifier(pattern))

    def oneof(self, str_list: list[Str]) -> Types:
        """This is the enum equivalent for jsonclasses. Values in the provided
        list are considered valid values.
        """
        return Types(self, OneOfModifier(str_list))

    def minlength(self, length: int) -> Types:
        """Values at fields marked with minlength should have a length which is
        not less than length.

        Args:
          length (int): The minimum length required for the value.

        Returns:
          Types: A new types chained with this marker.
        """
        return Types(self, MinlengthModifier(length))

    def maxlength(self, length: int) -> Types:
        """Values at fields marked with maxlength should have a length which is
        not greater than length.

        Args:
          length (int): The minimum length required for the value.

        Returns:
          Types: A new types chained with this marker.
        """
        return Types(self, MaxlengthModifier(length))

    def length(self, minlength: int, maxlength: Optional[int] = None) -> Types:
        """Fields marked with length should have a length which is between the
        two arguments. If only one argument is provided, the value length
        should be exactly that length.

        Args:
          minlength (int): The minimum length required for the value.
          maxlength (Optional[int]): The maximum length required for the value.

        Returns:
          Types: A new types chained with this marker.
        """
        return Types(self, LengthModifier(minlength, maxlength))

    @property
    def url(self) -> Types:
        """Fields marked with url should be valid url string. This is a
        modifier marker.
        """
        return Types(self, UrlModifier())

    @property
    def digit(self) -> Types:
        """Values of fields marked with digit should be valid digit string. This is a
        modifier marker.
        """
        return Types(self, DigitModifier())

    @property
    def alpha(self) -> Types:
        """Values of fields marked with alpha should be valid alpha string. This is a
        modifier marker.
        """
        return Types(self, AlphaModifier())

    @property
    def numeric(self) -> Types:
        """Values of fields marked with numeric should be valid numeric string. This is a
        modifier marker.
        """
        return Types(self, NumericModifier())

    @property
    def email(self) -> Types:
        """Values of fields marked with email should be valid email format. This is a
        modifier marker.
        """
        return Types(self, EmailModifier())

    @property
    def alnum(self) -> Types:
        """Values fields marked with alnum should be valid alnum strings. This is a
        modifier marker.
        """
        return Types(self, AlnumModifier())

    @property
    def int(self) -> Types:
        """Fields marked with int should be int type. This is a type marker.
        """
        return Types(self, IntModifier())

    @property
    def float(self) -> Types:
        """Fields marked with float should be float type. This is a type
        marker.
        """
        return Types(self, FloatModifier())

    def min(self, value: Float) -> Types:
        """Fields marked with min are tested again this value. Values less than
        the argument value are considered invalid.
        """
        return Types(self, MinModifier(value))

    def max(self, value: Float) -> Types:
        """Fields marked with max are tested again this value. Values greater
        than the argument value are considered invalid.
        """
        return Types(self, MaxModifier(value))

    def range(self, min_value: Float, max_value: Float) -> Types:
        """Fields marked with range are tested again argument values. Only
        values between the arguments range are considered valid.
        """
        return Types(self, RangeModifier(min_value, max_value))

    @property
    def negative(self) -> Types:
        """Fields marked with negative should have a value less than zero.
        """
        return Types(self, NegativeModifier())

    @property
    def positive(self) -> Types:
        """Fields marked with negative should have a value greater than zero.
        """
        return Types(self, PositiveModifier())

    @property
    def bool(self) -> Types:
        """Fields marked with bool should be bool type. This is a type marker.
        """
        return Types(self, BoolModifier())

    @property
    def date(self) -> Types:
        """Fields marked with date should be date type. This is a type marker.
        """
        return Types(self, DateModifier())

    @property
    def datetime(self) -> Types:
        """Fields marked with datetime should be datetime type. This is a type
        marker.
        """
        return Types(self, DatetimeModifier())

    def enum(self, enum_class: Union[type, str]) -> Types:
        """Fields marked with enum should be enum value of provided enum type.
        This is a type marker.
        """
        return Types(self, EnumModifier(enum_class))

    @property
    def inputall(self) -> Types:
        """Inputall makes enum field to accept all kinds of acceptable enum
        values in any forms.
        """
        return Types(self, InputAllModifier())

    @property
    def inputlname(self) -> Types:
        """Inputlname makes enum field to accept enum's lowercase name as
        input.
        """
        return Types(self, InputLnameModifier())

    @property
    def inputname(self) -> Types:
        """Inputlname makes enum field to accept enum's uppercase name as
        input.
        """
        return Types(self, InputNameModifier())

    @property
    def inputvalue(self) -> Types:
        """Inputlname makes enum field to accept enum's value as input.
        """
        return Types(self, InputValueModifier())

    @property
    def outputlname(self) -> Types:
        """Outputlname makes enum field to output lowercase name as display
        value.
        """
        return Types(self, OutputLnameModifier())

    @property
    def outputname(self) -> Types:
        """Outputname makes enum field to output uppercase name as display
        value.
        """
        return Types(self, OutputNameModifier())

    @property
    def outputvalue(self) -> Types:
        """Outputvalue makes enum field to output value as display value.
        """
        return Types(self, OutputValueModifier())

    def listof(self, item_types: Any) -> Types:
        """Fields marked with listof should be a list of the given type. This
        is a type marker.
        """
        return Types(self, ListOfModifier(item_types))

    def dictof(self, item_types: Any) -> Types:
        """Fields marked with listof should be a str keyed dict of the given
        type. This is a type marker.
        """
        return Types(self, DictOfModifier(item_types))

    def shape(self, item_types_map: dict[Str, Any]) -> Types:
        """Fields marked with shape are objects shaped with given shape. This
        is a type marker.
        """
        return Types(self, ShapeModifier(item_types_map))

    @property
    def strict(self) -> Types:
        """Shape fields marked with strict disallow undefined keys.
        """
        return Types(self, StrictModifier())

    def instanceof(self, json_object_class: Any) -> Types:
        """Fields marked with instance of are objects of given class.
        """
        return Types(self, InstanceOfModifier(json_object_class))

    def objof(self, jcls: Any) -> Types:
        """Fields marked with objof are objects of given class.
        """
        return Types(self, InstanceOfModifier(jcls))

    def union(self, type_list: list[Any]) -> Types:
        """Fields marked with union accepts value from these types.
        """
        return Types(self, UnionModifier(type_list))

    @property
    def any(self) -> Types:
        """Fields marked with any can be any value.
        """
        return Types(self, AnyModifier())

    @property
    def required(self) -> Types:
        """Fields marked with required are invalid when value is None.

        Returns:
          Types: A new types chained with this marker.
        """
        return Types(self, RequiredModifier())

    @property
    def nullable(self) -> Types:
        """Fields marked with nullable can be None. This is the default
        behavior even without this marker. It's the opposite to required
        marker. Values inside lists have implicitly required marker. Use this
        to allow null or None values inside lists.

        Returns:
          Types: A new types chained with this marker.
        """
        return Types(self, NullableModifier())

    @property
    def present(self) -> Types:
        """When validating, field marked with present, can not pass validation
        if it has a None value. This is useful for foreign key fields to do
        required validation.
        """
        return Types(self, PresentModifier())

    def presentwith(self, referring_key: str) -> Types:
        """Fields marked with presentwith modifier are forced presented if
        referring field is present. If referring field has None value, this
        field's value is optional. If referring field has non None value, value
        of this field is required.
        """
        return Types(self, PresentWithModifier(referring_key))

    def presentwithout(self, referring_keys: Union[str, list[str]]) -> Types:
        """Fields marked with presentwithout modifier are forced presented if
        referring field is not present. If referring field has None value, this
        field's value should be present. If referring field has non None value,
        value of this field is not forced to be present.
        """
        return Types(self, PresentWithoutModifier(referring_keys))

    def validate(self, validate_callable: Callable) -> Types:
        """The validate field mark takes a modifier callable as its sole
        argument. Use this to define custom field value validations.

        Args:
            validate_callable (Callable): The validate callable takes up to 2
            arguments, which are value and context. Returning None or True
            means the value is valid, while returning a str message or False
            means validation failed.

        Returns:
            Types: A new types chained with this marker.
        """
        return Types(self, ValidateModifier(validate_callable))

    def op(self, op_callable: Callable) -> Types:
        """Operator modifier validates value against the operator object user
        passed in. This modifier is special and doesn't bypass None value. If
        the operator is not present, this modifier fails.

        Args:
            op_callable (Callable): The op callable takes 1 to 4 arguments. The
            first is the operator object, the second is the object being operated,
            the third is the value of the field, the fourth is the validating context.
            Returning None or True means the value is valid, while returning a str
            message or False means validation failed.

        Returns:
            Types: A new types chained with this marker.
        """
        return Types(self, OpModifier(op_callable))

    def compare(self, compare_callable: Callable) -> Types:
        """The compare field mark takes a modifier callable as its sole
        argument. If value of compared fields are changed, this modifier is
        called with 2 to 5 arguments.

        Args:
            compare_callable (Callable): The compare callable. Arg 1 is the old
            value, arg 2 is the new value, arg 3 is key path, arg 4 is the
            object, arg 5 is the context. Returning None means the value is
            valid, while returning a str message means validation failed.

        Returns:
            Types: A new types chained with this marker.
        """
        return Types(self,
                     ResetModifier(),
                     CompareModifier(compare_callable))

    # transformers

    def default(self, value: Any) -> Types:
        """During initialization, if values of fields with default are not
        provided. The default value is used instead of leaving blank.

        Args:
          value (any): The default value of this field. If the value is
          callable, it's return value is used.

        Returns:
          Types: A new types chained with this marker.
        """
        return Types(self, DefaultModifier(value))

    def truncate(self, max_length: Int) -> Types:
        """During initialization and set, if string value is too long, it's
        truncated to argument max length.

        Args:
          max_length (int): The allowed max length of the field value.

        Returns:
          Types: A new types chained with this marker.
        """
        return Types(self, EagerModifier(), TruncateModifier(max_length))

    @property
    def trim(self) -> Types:
        """This marker will trim string value. Remove leading and trailing
        whitespaces.

        Returns:
            Types: A new types chained with this marker.
        """
        return Types(self, EagerModifier(), TrimModifier())

    @property
    def totitle(self) -> Types:
        """This marker titlizes strings.

        Returns:
            Types: A new types chained with this marker.
        """
        return Types(self, EagerModifier(), ToTitleModifier())

    @property
    def tocap(self) -> Types:
        """This marker capitalizes strings.

        Returns:
            Types: A new types chained with this marker.
        """
        return Types(self, EagerModifier(), ToCapModifier())

    @property
    def tolower(self) -> Types:
        """This marker lowercasefies strings.

        Returns:
            Types: A new types chained with this marker.
        """
        return Types(self, EagerModifier(), ToLowerModifier())

    @property
    def toupper(self) -> Types:
        """This marker uppercasefies strings.

        Returns:
            Types: A new types chained with this marker.
        """
        return Types(self, EagerModifier(), ToUpperModifier())

    def transform(self, transformer: Callable) -> Types:
        """This mark applies transfromer on the value. When value is None, the
        transformer is not called. This class barely means to transform. Use
        default mark with a callable to assign calculated default value.

        Args:
          transformer (Callable): This transformer function takes one argument
          which is the current value of the field.

        Returns:
          Types: A new types chained with this marker.
        """
        return Types(self, EagerModifier(), TransformModifier(transformer))

    def uploader(self, uploader: Callable) -> Types:
        """Uploader is barely a syntax alias for transformer. Uploaded files
        can be processed through a transformer and get url attached to this
        object.

        Args:
          transformer (Callable): This transformer function takes one argument
          which is the current value of the field.

        Returns:
          Types: A new types chained with this marker.
        """
        return Types(self, EagerModifier(), TransformModifier(uploader))

    def asop(self, asop_transformer: Callable) -> Types:
        """Asop marker assigns transformed operator value to this field. When
        the operator is not present, a ValidationException is raised.

        Args:
            asop_transformer (Callable): This transformer function takes 1 to
            3 arguments. The first one is the opeartor, the second one is the
            value of the field, the third is the transforming context.

        Returns:
            Types: A new types chained with this marker.
        """
        return Types(self, AsopModifier(asop_transformer))

    @property
    def asopd(self) -> Types:
        """Asopd marker assigns operator value directly to this field without
        any transforming. When the operator  is not present, a
        ValidationException is raised.

        Returns:
            Types: A new types chained with this marker.
        """
        return Types(self, AsopdModifier())

    def setonsave(self, setter: Callable) -> Types:
        """Setonsave marker marks a field to be updated just before serializing
        into the database if this field is modified and to be serialized.

        Args:
          setter (Callable): This setter function takes zero or one argument
          which is the current value of the field.

        Returns:
          Types: A new types chained with this marker.
        """
        return Types(self, PreserializeModifier(), SetOnSaveModifier(setter))

    def onsave(self, callback: Callable) -> Types:
        """Onsave inserts a callback into the modifier chain. If save action
        is triggered, the callback is called with the current value.

        Args:
            callback (Callable): This callback function takes one argument
            which is the current value of the field.

        Returns:
            Types: A new types chained with this marker.
        """
        return Types(self, OnSaveModifier(callback))

    def onupdate(self, callback: Callable) -> Types:
        """Onupdate is a callback modifier. If value updated when saving, this
        callback is called with the current value or both values.

        Args:
            callback (Callable): A callable which takes arguments.

        Returns:
            Types: A new types chained with this marker.
        """
        return Types(self, ResetModifier(), OnUpdateModifier(callback))

    def onwrite(self, callback: Callable) -> Types:
        """Onwrite is a callback modifier. Whenever a new value is being
        serialized into the database, this is called.

        Args:
            callback (Callable): A callable which takes arguments.

        Returns:
            Types: A new types chained with this marker.
        """
        return Types(self, OnWriteModifier(callback))

    @property
    def nonnull(self) -> Types:
        """This marker is a instructional transformer designated for shape, dictof
        and listof. This is not a modifier. To mark a field is required and should
        not be null, use `required` instead. This transformer should be used right
        before shape, dictof and listof, to given an instruction of not leaving
        null for the field.

        Returns:
          Types: A new types chained with this marker.
        """
        return Types(self, NonnullModifier())

    def _unresolved(self: Types, arg: str) -> Types:
        """This marker marks unresolved status. This is used internally. Do not
        use this.
        """
        return Types(self, UnresolvedModifier(arg))


types = Types()
"""The root of the types marker. To mark an field with type annotation,
accessor annotation, modifier annotation and transformer annotation, use types
like this:

  @jsonclass
  class MyObject:
    my_field_one: bool = types.bool.readonly.required
    my_field_two: password = types.bool.writeonly.length(8, 16).transform(salt\
).required
"""
