import os
import pickle



class StateMemory:
    def __init__(self):
        self._states = {}

    async def add_state(self, chat_id, state):
        """
        Add a state.
        :param chat_id:
        :param state: new state
        """
        if chat_id in self._states:
            
            self._states[chat_id]['state'] = state
        else:
            self._states[chat_id] = {'state': state,'data': {}}

    async def current_state(self, chat_id):
        """Current state"""
        if chat_id in self._states: return self._states[chat_id]['state']
        else: return False

    async def delete_state(self, chat_id):
        """Delete a state"""
        self._states.pop(chat_id)

    def _get_data(self, chat_id):
        return self._states[chat_id]['data']

    async def set(self, chat_id, new_state):
        """
        Set a new state for a user.
        :param chat_id:
        :param new_state: new_state of a user
        """
        await self.add_state(chat_id,new_state)

    async def _add_data(self, chat_id, key, value):
        result = self._states[chat_id]['data'][key] = value
        return result

    async def finish(self, chat_id):
        """
        Finish(delete) state of a user.
        :param chat_id:
        """
        await self.delete_state(chat_id)

    def retrieve_data(self, chat_id):
        """
        Save input text.

        Usage:
        with bot.retrieve_data(message.chat.id) as data:
            data['name'] = message.text

        Also, at the end of your 'Form' you can get the name:
        data['name']
        """
        return StateContext(self, chat_id)


class StateFile:
    """
    Class to save states in a file.
    """
    def __init__(self, filename):
        self.file_path = filename

    async def add_state(self, chat_id, state):
        """
        Add a state.
        :param chat_id:
        :param state: new state
        """
        states_data = self._read_data()
        if chat_id in states_data:
            states_data[chat_id]['state'] = state
            return await self._save_data(states_data)
        else:
            new_data = states_data[chat_id] = {'state': state,'data': {}}
            return await self._save_data(states_data)


    async def current_state(self, chat_id):
        """Current state."""
        states_data = self._read_data()
        if chat_id in states_data: return states_data[chat_id]['state']
        else: return False

    async def delete_state(self, chat_id):
        """Delete a state"""
        states_data = self._read_data()
        states_data.pop(chat_id)
        await self._save_data(states_data)

    def _read_data(self):
        """
        Read the data from file.
        """
        file = open(self.file_path, 'rb')
        states_data = pickle.load(file)
        file.close()
        return states_data

    def _create_dir(self):
        """
        Create directory .save-handlers.
        """
        dirs = self.file_path.rsplit('/', maxsplit=1)[0]
        os.makedirs(dirs, exist_ok=True)
        if not os.path.isfile(self.file_path):
            with open(self.file_path,'wb') as file:
                pickle.dump({}, file)
        
    async def _save_data(self, new_data):
        """
        Save data after editing.
        :param new_data:
        """
        with open(self.file_path, 'wb+') as state_file:
            pickle.dump(new_data, state_file, protocol=pickle.HIGHEST_PROTOCOL)
        return True

    def _get_data(self, chat_id):
        return self._read_data()[chat_id]['data']

    async def set(self, chat_id, new_state):
        """
        Set a new state for a user.
        :param chat_id:
        :param new_state: new_state of a user
        
        """
        await self.add_state(chat_id,new_state)

    async def _add_data(self, chat_id, key, value):
        states_data = self._read_data()
        result = states_data[chat_id]['data'][key] = value
        await self._save_data(result)

        return result

    async def finish(self, chat_id):
        """
        Finish(delete) state of a user.
        :param chat_id:
        """
        await self.delete_state(chat_id)

    def retrieve_data(self, chat_id):
        """
        Save input text.

        Usage:
        with bot.retrieve_data(message.chat.id) as data:
            data['name'] = message.text

        Also, at the end of your 'Form' you can get the name:
        data['name']
        """
        return StateFileContext(self, chat_id)


class StateContext:
    """
    Class for data.
    """
    def __init__(self , obj: StateMemory, chat_id) -> None:
        self.obj = obj
        self.chat_id = chat_id
        self.data = obj._get_data(chat_id)

    async def __aenter__(self):
        return self.data

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        return

class StateFileContext:
    """
    Class for data.
    """
    def __init__(self , obj: StateFile, chat_id) -> None:
        self.obj = obj
        self.chat_id = chat_id
        self.data = None

    async def __aenter__(self):
        self.data = self.obj._get_data(self.chat_id)
        return self.data

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        old_data = self.obj._read_data()
        for i in self.data:
            old_data[self.chat_id]['data'][i] = self.data.get(i)
        await self.obj._save_data(old_data)

        return


class BaseMiddleware:
    """
    Base class for middleware.

    Your middlewares should be inherited from this class.
    """
    def __init__(self):
        pass

    async def pre_process(self, message, data):
        raise NotImplementedError
    async def post_process(self, message, data, exception):
        raise NotImplementedError