# local imports
from shep.error import (
        StateExists,
        StateInvalid,
        StateItemExists,
        StateItemNotFound,
        )


class State:

    def __init__(self, bits, logger=None):
        self.__bits = bits
        self.__limit = (1 << bits) - 1
        self.__c = 0
        self.NEW = 0

        self.__reverse = {0: self.NEW}
        self.__keys = {self.NEW: []}
        self.__keys_reverse = {}
        self.__contents = {}


    def __is_pure(self, v):
        if v == 0:
            return True
        c = 1
        for i in range(self.__bits):
            if c & v > 0:
                break
            c <<= 1
        return c == v


    def __check_name_valid(self, k):
        if not k.isalpha():
            raise ValueError('only alpha')
        return k.upper()


    def __check_name(self, k):
        k = self.__check_name_valid(k) 
            
        try:
            getattr(self, k)
            raise StateExists(k)
        except AttributeError:
            pass
        return k


    def __check_valid(self, v):
        v = int(v)
        if self.__reverse.get(v):
            raise StateExists(v)
        return v


    def __check_limit(self, v):
        if v > self.__limit:
            raise OverflowError(v)


    def __check_value(self, v):
        v = self.__check_valid(v)
        self.__check_limit(v) 
        return v


    def __check_value_cursor(self, v):
        v = self.__check_valid(v)
        if v > 1 << self.__c:
            raise StateInvalid(v)
        return v


    def __set(self, k, v):
        setattr(self, k, v)
        self.__reverse[v] = k
        self.__c += 1


    def __check_key(self, item):
        if self.__keys_reverse.get(item) != None:
            raise StateItemExists(item)


    def __add_state_list(self, state, item):
        if self.__keys.get(state) == None:
            self.__keys[state] = []
        self.__keys[state].append(item)
        self.__keys_reverse[item] = state


    def __state_list_index(self, item, state_list):
        idx = -1
        try:
            idx = state_list.index(item)
        except ValueError:
            pass

        if idx == -1:
            raise StateCorruptionError() # should have state int here as value

        return idx


    def add(self, k):
        v = 1 << self.__c
        k = self.__check_name(k)
        v = self.__check_value(v)
        self.__set(k, v)
        

    def alias(self, k, v):
        k = self.__check_name(k)
        v = self.__check_value_cursor(v)
        if self.__is_pure(v):
            raise ValueError('use add to add pure values')
        self.__set(k, v)


    def all(self):
        l = []
        for k in dir(self):
            if k[0] == '_':
                continue
            if k.upper() != k:
                continue
            l.append(k)
        l.sort()
        return l


    def name(self, v):
        if v == None or v == 0:
            return 'NEW'
        k = self.__reverse.get(v)
        if k == None:
            raise StateInvalid(v)
        return k


    def from_name(self, k):
        k = self.__check_name_valid(k)
        return getattr(self, k)


    def match(self, v, pure=False):
        alias = None
        if not pure:
            alias = self.__reverse.get(v)

        r = []
        c = 1
        for i in range(self.__bits):
            if v & c > 0:
                try:
                    k = self.__reverse[c]
                    r.append(k)
                except KeyError:
                    pass
            c <<= 1

        return (alias, r,)

    
    def put(self, key, state=None, contents=None, force=False):
        if state == None:
            state = self.NEW
        elif self.__reverse.get(state) == None:
            raise StateInvalid(state)
        try:
            self.__check_key(key)
        except StateItemExists as e:
            if not force:
                raise(e)
        self.__add_state_list(state, key)
        if contents != None:
            self.__contents[key] = contents

        return state
                                

    def move(self, key, to_state):
        current_state = self.__keys_reverse.get(key)
        if current_state == None:
            raise StateItemNotFound(key)

        new_state = self.__reverse.get(to_state)
        if new_state == None:
            raise StateInvalid(to_state)

        return self.__move(key, current_state, to_state)


    def __move(self, key, from_state, to_state):
        current_state_list = self.__keys.get(from_state)
        if current_state_list == None:
            raise StateCorruptionError(current_state)

        idx = self.__state_list_index(key, current_state_list)

        new_state_list = self.__keys.get(to_state)
        if current_state_list == None:
            raise StateCorruptionError(to_state)

        self.__add_state_list(to_state, key)
        current_state_list.pop(idx) 

        return to_state


    def set(self, key, or_state, content=None):
        if not self.__is_pure(or_state):
            raise ValueError('can only apply using single bit states')

        current_state = self.__keys_reverse.get(key)
        if current_state == None:
            raise StateItemNotFound(key)

        to_state = current_state | or_state
        new_state = self.__reverse.get(to_state)
        if new_state == None:
            raise StateInvalid('resulting to state is unknown: {}'.format(to_state))

        return self.__move(key, current_state, to_state)

    
    def unset(self, key, not_state):
        if not self.__is_pure(not_state):
            raise ValueError('can only apply using single bit states')

        current_state = self.__keys_reverse.get(key)
        if current_state == None:
            raise StateItemNotFound(key)

        to_state = current_state & (~not_state)
        if to_state == current_state:
            raise ValueError('invalid change for state {}: {}'.format(key, not_state))

        new_state = self.__reverse.get(to_state)
        if new_state == None:
            raise StateInvalid('resulting to state is unknown: {}'.format(to_state))

        return self.__move(key, current_state, to_state)


    def state(self, key):
        state = self.__keys_reverse.get(key)
        if state == None:
            raise StateItemNotFound(key)
        return state


    def get(self, key=None):
        return self.__contents.get(key)


    def list(self, state):
        try:
            return self.__keys[state]
        except KeyError:
            return []


    def sync(self, state):
        pass


    def path(self, state, key=None):
        return None


    def peek(self, key):
        state = self.__keys_reverse.get(key)
        if state == None:
            raise StateItemNotFound(key)
        if not self.__is_pure(state):
            raise StateInvalid('cannot run next on an alias state')
       
        if state == 0:
            state = 1
        else:
            state <<= 1
        if state > self.__c:
            raise StateInvalid('unknown state {}'.format(state))

        return state


    def next(self, key):
        from_state = self.state(key)
        new_state = self.peek(key)
        return self.__move(key, from_state, new_state)


    def replace(self, key, contents):
        self.state(key)
        self.__contents[key] = contents
