import os
from enum import Enum
import json
from typing import List

from .from_json import from_json
from .config import AppConfiguration
from .cache_manager import CacheManager
from .server.api_objects import Child


class RepeatType(Enum):
    NO_REPEAT = 0
    REPEAT_QUEUE = 1
    REPEAT_SONG = 2

    @property
    def icon(self):
        icon_name = [
            'repeat',
            'repeat-symbolic',
            'repeat-song-symbolic',
        ][self.value]
        return 'media-playlist-' + icon_name

    def as_mpris_loop_status(self):
        return ['None', 'Playlist', 'Track'][self.value]

    @staticmethod
    def from_mpris_loop_status(loop_status):
        return {
            'None': RepeatType.NO_REPEAT,
            'Track': RepeatType.REPEAT_SONG,
            'Playlist': RepeatType.REPEAT_QUEUE,
        }[loop_status]


class ApplicationState:
    """
    Represents the state of the application. In general, there are two things
    that are stored here: configuration, and UI state.

    Configuration is stored in ``config`` which is an ``AppConfiguration``
    object. UI state is stored as separate properties on this class.

    Configuration is stored to disk in $XDG_CONFIG_HOME/sublime-music. State is
    stored in $XDG_CACHE_HOME. Nothing in state should be assumed to be
    permanent. State need not be saved, the ``to_json`` and ``from_json``
    functions define what part of the state will be saved across application
    loads.
    """
    config: AppConfiguration = AppConfiguration()
    current_song: Child = None
    config_file: str = None
    playing: bool = False
    play_queue: List[str] = []
    old_play_queue: List[str] = []
    _volume: dict = {'this device': 100}
    is_muted: bool = False
    repeat_type: RepeatType = RepeatType.NO_REPEAT
    shuffle_on: bool = False
    song_progress: float = 0
    current_device: str = 'this device'
    current_tab: str = 'albums'
    selected_album_id: str = None
    selected_artist_id: str = None
    selected_playlist_id: str = None

    # State for Album sort.
    current_album_sort: str = 'random'
    current_album_genre: str = 'Rock'
    current_album_alphabetical_sort: str = 'name'
    current_album_from_year: int = 2010
    current_album_to_year: int = 2020

    active_playlist_id: str = None

    def to_json(self):
        current_song = (
            self.current_song.id if
            (hasattr(self, 'current_song')
             and self.current_song is not None) else None)
        # TODO this really sucks. We should fix this.
        return {
            'current_song':
            current_song,
            'play_queue':
            getattr(self, 'play_queue', None),
            'old_play_queue':
            getattr(self, 'old_play_queue', None),
            '_volume':
            getattr(self, '_volume', {}),
            'is_muted':
            getattr(self, 'is_muted', None),
            'repeat_type':
            getattr(self, 'repeat_type', RepeatType.NO_REPEAT).value,
            'shuffle_on':
            getattr(self, 'shuffle_on', None),
            'song_progress':
            getattr(self, 'song_progress', None),
            'current_device':
            getattr(self, 'current_device', 'this device'),
            'current_tab':
            getattr(self, 'current_tab', 'albums'),
            'selected_album_id':
            getattr(self, 'selected_album_id', None),
            'selected_artist_id':
            getattr(self, 'selected_artist_id', None),
            'selected_playlist_id':
            getattr(self, 'selected_playlist_id', None),
            'current_album_sort':
            getattr(self, 'current_album_sort', None),
            'current_album_genre':
            getattr(self, 'current_album_genre', None),
            'current_album_alphabetical_sort':
            getattr(self, 'current_album_alphabetical_sort', None),
            'current_album_from_year':
            getattr(self, 'current_album_from_year', None),
            'current_album_to_year':
            getattr(self, 'current_album_to_year', None),
            'active_playlist_id':
            getattr(self, 'active_playlist_id', None),
        }

    def load_from_json(self, json_object):
        current_song_id = json_object.get('current_song') or None
        if current_song_id and CacheManager.cache:
            self.current_song = CacheManager.cache['song_details'].get(
                current_song_id)
        else:
            self.current_song = None

        self.play_queue = json_object.get('play_queue', [])
        self.old_play_queue = json_object.get('old_play_queue', [])
        self._volume = json_object.get('_volume', {'this device': 100})
        self.is_muted = json_object.get('is_muted', False)
        self.repeat_type = RepeatType(json_object.get('repeat_type', 0))
        self.shuffle_on = json_object.get('shuffle_on', False)
        self.song_progress = json_object.get('song_progress', 0.0)
        self.current_device = json_object.get('current_device', 'this device')
        self.current_tab = json_object.get('current_tab', 'albums')
        self.selected_album_id = json_object.get('selected_album_id', None)
        self.selected_artist_id = json_object.get('selected_artist_id', None)
        self.selected_playlist_id = json_object.get(
            'selected_playlist_id', None)
        self.current_album_sort = json_object.get(
            'current_album_sort', 'random')
        self.current_album_genre = json_object.get(
            'current_album_genre', 'Rock')
        self.current_album_alphabetical_sort = json_object.get(
            'current_album_alphabetical_sort', 'name')
        self.current_album_from_year = json_object.get(
            'current_album_from_year', 2010)
        self.current_album_to_year = json_object.get(
            'current_album_to_year', 2020)
        self.active_playlist_id = json_object.get('active_playlist_id', None)

    def load(self):
        self.config = self.get_config(self.config_file)

        if self.config.server is not None:
            # Reset the CacheManager.
            CacheManager.reset(self.config, self.config.server)

        if (self.config.server is not None
                and os.path.exists(self.state_filename)):
            with open(self.state_filename, 'r') as f:
                try:
                    self.load_from_json(json.load(f))
                except json.decoder.JSONDecodeError:
                    # Who cares, it's just state.
                    pass
        else:
            self.load_from_json({})

    def save(self):
        # Make the necessary directories before writing the state.
        os.makedirs(os.path.dirname(self.state_filename), exist_ok=True)

        # Save the state
        with open(self.state_filename, 'w+') as f:
            f.write(json.dumps(self.to_json(), indent=2, sort_keys=True))

    def save_config(self):
        # Make the necessary directories before writing the config.
        os.makedirs(os.path.dirname(self.config_file), exist_ok=True)

        with open(self.config_file, 'w+') as f:
            f.write(
                json.dumps(self.config.to_json(), indent=2, sort_keys=True))

    def get_config(self, filename: str) -> AppConfiguration:
        if not os.path.exists(filename):
            return AppConfiguration()

        with open(filename, 'r') as f:
            try:
                return from_json(AppConfiguration, json.load(f))
            except json.decoder.JSONDecodeError:
                return AppConfiguration()

    @property
    def state_filename(self):
        default_cache_location = (
            os.environ.get('XDG_DATA_HOME')
            or os.path.expanduser('~/.local/share'))
        return os.path.join(
            default_cache_location,
            'sublime-music',
            CacheManager.calculate_server_hash(self.config.server),
            'state.yaml',
        )

    @property
    def volume(self):
        return self._volume.get(self.current_device, 100)

    @volume.setter
    def volume(self, value):
        self._volume[self.current_device] = value
