# Released under the MIT License. See LICENSE for details.
#
"""ClientEffect related functionality.

.. warning::

  This is an internal api and subject to change at any time. Do not use
  it in mod code.
"""

import datetime
from enum import Enum
from dataclasses import dataclass, field
from typing import Annotated, override, assert_never

from efro.dataclassio import ioprepped, IOAttrs, IOMultiType


class EffectTypeID(Enum):
    """Type ID for each of our subclasses."""

    UNKNOWN = 'u'
    LEGACY_SCREEN_MESSAGE = 'm'
    SCREEN_MESSAGE = 'sm'
    SOUND = 's'
    DELAY = 'd'
    CHEST_WAIT_TIME_ANIMATION = 't'
    TICKETS_ANIMATION = 'ta'
    TOKENS_ANIMATION = 'toa'


class Effect(IOMultiType[EffectTypeID]):
    """Something that can happen on the client.

    This can include screen messages, sounds, visual effects, etc.
    """

    @override
    @classmethod
    def get_type_id(cls) -> EffectTypeID:
        # Require child classes to supply this themselves. If we did a
        # full type registry/lookup here it would require us to import
        # everything and would prevent lazy loading.
        raise NotImplementedError()

    @override
    @classmethod
    def get_type(cls, type_id: EffectTypeID) -> type[Effect]:
        """Return the subclass for each of our type-ids."""
        # pylint: disable=cyclic-import
        # pylint: disable=too-many-return-statements

        t = EffectTypeID
        if type_id is t.UNKNOWN:
            return Unknown
        if type_id is t.LEGACY_SCREEN_MESSAGE:
            return LegacyScreenMessage
        if type_id is t.SCREEN_MESSAGE:
            return ScreenMessage
        if type_id is t.SOUND:
            return PlaySound
        if type_id is t.DELAY:
            return Delay
        if type_id is t.CHEST_WAIT_TIME_ANIMATION:
            return ChestWaitTimeAnimation
        if type_id is t.TICKETS_ANIMATION:
            return TicketsAnimation
        if type_id is t.TOKENS_ANIMATION:
            return TokensAnimation

        # Important to make sure we provide all types.
        assert_never(type_id)

    @override
    @classmethod
    def get_unknown_type_fallback(cls) -> Effect:
        # If we encounter some future message type we don't know
        # anything about, drop in a placeholder.
        return Unknown()


@ioprepped
@dataclass
class Unknown(Effect):
    """Fallback substitute for types we don't recognize."""

    @override
    @classmethod
    def get_type_id(cls) -> EffectTypeID:
        return EffectTypeID.UNKNOWN


@ioprepped
@dataclass
class LegacyScreenMessage(Effect):
    """Display a screen-message (Legacy version).

    This will be processed as an Lstr with translation category
    'serverResponses'.

    When possible, migrate to using :class:`ScreenMessage`.
    """

    message: Annotated[str, IOAttrs('m')]
    subs: Annotated[list[str], IOAttrs('s', store_default=False)] = field(
        default_factory=list
    )
    color: Annotated[
        tuple[float, float, float], IOAttrs('c', store_default=False)
    ] = (1.0, 1.0, 1.0)

    @override
    @classmethod
    def get_type_id(cls) -> EffectTypeID:
        return EffectTypeID.LEGACY_SCREEN_MESSAGE


@ioprepped
@dataclass
class ScreenMessage(Effect):
    """Display a screen-message.

    Supported on engine build 22606 or newer.

    This version does no translation by default (expecting translation
    to happen server-side). Pass a Lstr json string and set is_lstr=True
    for client-side translation.
    """

    message: Annotated[str, IOAttrs('m')]
    color: Annotated[
        tuple[float, float, float], IOAttrs('c', store_default=False)
    ] = (1.0, 1.0, 1.0)
    is_lstr: Annotated[bool, IOAttrs('l', store_default=False)] = False

    @override
    @classmethod
    def get_type_id(cls) -> EffectTypeID:
        return EffectTypeID.SCREEN_MESSAGE


class Sound(Enum):
    """Sounds that can be played."""

    UNKNOWN = 'u'
    CASH_REGISTER = 'c'
    ERROR = 'e'
    POWER_DOWN = 'p'
    GUN_COCKING = 'g'


@ioprepped
@dataclass
class PlaySound(Effect):
    """Play a sound."""

    sound: Annotated[Sound, IOAttrs('s', enum_fallback=Sound.UNKNOWN)]
    volume: Annotated[float, IOAttrs('v', store_default=False)] = 1.0

    @override
    @classmethod
    def get_type_id(cls) -> EffectTypeID:
        return EffectTypeID.SOUND


@ioprepped
@dataclass
class ChestWaitTimeAnimation(Effect):
    """Animate chest wait time changing."""

    chestid: Annotated[str, IOAttrs('c')]
    duration: Annotated[float, IOAttrs('u')]
    startvalue: Annotated[datetime.datetime, IOAttrs('o')]
    endvalue: Annotated[datetime.datetime, IOAttrs('n')]

    @override
    @classmethod
    def get_type_id(cls) -> EffectTypeID:
        return EffectTypeID.CHEST_WAIT_TIME_ANIMATION


@ioprepped
@dataclass
class TicketsAnimation(Effect):
    """Animate tickets count."""

    duration: Annotated[float, IOAttrs('u')]
    startvalue: Annotated[int, IOAttrs('s')]
    endvalue: Annotated[int, IOAttrs('e')]

    @override
    @classmethod
    def get_type_id(cls) -> EffectTypeID:
        return EffectTypeID.TICKETS_ANIMATION


@ioprepped
@dataclass
class TokensAnimation(Effect):
    """Animate tokens count."""

    duration: Annotated[float, IOAttrs('u')]
    startvalue: Annotated[int, IOAttrs('s')]
    endvalue: Annotated[int, IOAttrs('e')]

    @override
    @classmethod
    def get_type_id(cls) -> EffectTypeID:
        return EffectTypeID.TOKENS_ANIMATION


@ioprepped
@dataclass
class Delay(Effect):
    """Delay effect processing."""

    seconds: Annotated[float, IOAttrs('s')]

    @override
    @classmethod
    def get_type_id(cls) -> EffectTypeID:
        return EffectTypeID.DELAY
