import world_map.cell_and_map_classes as cell_and_map_classes
import local_settings, visualization, helpers
import units_and_buildings.unit_classes as U_classes
from units_and_buildings.function_argument_requirer import function_argument_requirer
import constants as CONST

from globals import G

class player_map_cell:
    def __init__(self, link):
        # link is key from G.world_map
        # ??? units are {'players' : list of indexes from self.units}

        self.link = link
        self.wm_cell = G.world_map.get_cell(link)
        self.visual_info = [{
                            'ph_template' : self.wm_cell.ph_template,
                             'icon' : self.wm_cell.icon,
                             'contour': self.wm_cell.contour
                            },
                            False]
        # second one is previous visual_info
        self.units = self.wm_cell.units
    def update(self, another):
        # another is new player_map_cell object
        self.visual_info = [another.visual_info[0], self.visual_info[0]]
        self.units = another.units
    def get_changes(self):
        self.visual_info[0] = {'ph_template' : G.world_map.get_cell(self.link).ph_template,
                               'icon' : G.world_map.get_cell(self.link).icon,
                               'contour': G.world_map.get_cell(self.link).contour}

def get_adj_cells(wm_cell, pl_cell):
    # returns new cells on user map
    # list of [player cell coords (string), player_map_cell object]
    # adds new cells to user map
    layer, x, y = pl_cell.split(', ')
    layer = int(layer)
    x = int(x)
    y = int(y)
    new_cells = []
    for i in wm_cell.adjacent:
        adj_link = join(wm_cell.adjacent[i], ', ')
        adj_wm_cell = G.world_map.get_cell(adj_link)
        adj_pl_cell = one_step_on_user_map(pl_cell, i)
        adj_units = adj_wm_cell.units
        new_cells.append([adj_pl_cell, player_map_cell(adj_link, adj_units)])
    return new_cells


class player:

    def __init__(self, info, first_cell):
        self.name = info['name']
        self.color = info['color']

        self.is_died = False
        self.is_players_turn = False
        self.queue = {self.change_vision_area: []}   # function: list of arguments; when turn starts, function calls for each argument one by one

        self.init_command_list()

        self.current_unit = None
        self.map = {'0, 0, 0': player_map_cell(first_cell)}
        '''key - stringed coords, element - payer_map_cell object'''
        self.units = []
        '''list of unit instances'''
        self.cities = {}
        '''key - pl_cell, value - city object'''

        self.V = visualization.player_interface(local_settings.screen_size, self)
        self.V.update_cells(['0, 0, 0'], self.map)

        self.new_unit(U_classes.settler,
                      'settler',
                      G.world_map.get_cell(first_cell),
                      '0, 0, 0',
                      2)

        self.new_unit(U_classes.mage,
                      'mage',
                      G.world_map.get_cell(first_cell),
                      '0, 0, 0',
                      3)

        # self.wait_for_command()
        self.V.is_hidden = False

    def init_command_list(self):
        '''keycode : [function, args]'''
        self.list_of_commands = {
            # move unit
            'move right': [self.move_unit_one_step, ['_R']],
            'move left': [self.move_unit_one_step, ['_L']],
            'move up-right': [self.move_unit_one_step, ['UR']],
            'move down-right': [self.move_unit_one_step, ['DR']],
            'move up-left': [self.move_unit_one_step, ['UL']],
            'move down-left': [self.move_unit_one_step, ['DL']],

            # unit special action
            'unit special action': [self.execute_current_unit_special_action, []],

            'get cell info': [self.get_cell_info, []],

            'next unit': [self.next_unit, []],

            'end turn': [self.end_turn, []]
        }

    def wait_for_command(self):
        while self.is_players_turn:
            command, c_arg = self.V.wait_for_command()
            func, arg = self.list_of_commands[command]
            arg = c_arg + arg
            func(*arg)

    def check_if_died(self):
        if len(self.units) == 0:
            return True
        else:
            return False

    def start_turn(self):
        self.is_players_turn = True
        for unit in self.units:
            unit.restore_action_points()
            if unit.status == CONST.unit_status.moved:
                unit.status = CONST.unit_status.should_be_moved

        for function in self.queue:
            arg_list = self.queue[function]
            for arg in arg_list:
                function(*arg)
        self.queue = {self.change_vision_area: []}

        if self.check_if_died():
            self.is_died = True
            G.delete_player(self)
            return

        self.V.update_map_on_screen()
        if self.current_unit == None:
            self.V.update_info_bar(None)
        else:
            self.V.update_info_bar(self.get_current_user_obj().unit_bar_info)
            self.center_camera_on_current_unit()
        self.wait_for_command()

    def end_turn(self):
        for i in range(len(self.units)):
            if self.units[i].status == CONST.unit_status.should_be_moved:
                self.set_current_unit(i)
                return

        self.is_players_turn = False

    def get_cell(self, stringified_coords):
        return self.map[stringified_coords]

    def get_world_cell(self, stringified_coords):
        return G.world_map.get_cell(self.get_cell(stringified_coords).link)

    def get_unit_by_index(self, index):
        return self.units[index]

    def add_unit(self, pl_cell, unit_inst):
        index = len(self.units)
        self.units.append(unit_inst)
        self.set_current_unit(index)

    def remove_unit(self, unit_inst):
        index = self.units.index(unit_inst)
        self.get_cell(unit_inst.pl_cell).get_changes()
        # todo - what is it???
        self.V.update_cells([unit_inst.pl_cell], self.map)
        self.units.pop(index)
        if self.current_unit == index:
            self.current_unit = None
            self.V.update_info_bar(None)
        elif self.current_unit is not None and self.current_unit > index:
            self.current_unit -= 1

    def update_map(self, new_cells):
        '''new_cells is list of {pl_coords: string, wm_cell: world_map_cell instance]
        return list of keys of updated cells'''
        updated_cells = []
        for i in new_cells:
            updated_cells.append(i['pl_coords'])
            pl_cell = player_map_cell(i['wm_cell'].name)
            if i['pl_coords'] in self.map:
                self.map[i['pl_coords']].update(pl_cell)
            else:
                self.map[i['pl_coords']] = pl_cell
        return updated_cells

    def set_current_unit(self, index):
        self.current_unit = index
        self.V.update_info_bar(self.units[index].unit_bar_info)
        self.center_camera_on_current_unit()

    def center_camera_on_current_unit(self):
        self.V.center_camera_on_cell(self.units[self.current_unit].pl_cell)

    def get_current_user_obj(self):
        try:
            return self.units[self.current_unit]
        except:
            return None

    def require_value(self, type, description):
        # todo - such working with types looks ugly
        if str(type) == "<class 'world_map.cell_and_map_classes.cell'>":
            while True:
                got_coords = self.V.choose_cell(description)
                # todo - here should be checked if cell reachable
                if got_coords in self.map:
                    return self.get_world_cell(got_coords)
                else:
                    self.V.message('not a cell')
        elif str(type) == "<class 'int'>" or str(type) == "<class 'str'>":
            got = self.V.require_value(type, description)
        else:
            raise Exception('Incorrect type of value should be required from player')
        return got

    def add_city(self, new_city):
        # todo - add it
        pass

    def remove_city(self, city_inst):
        # todo - remove it!
        pass

    def next_unit(self):
        if self.current_unit is not None:
            self.current_unit += 1
            if self.current_unit >= len(self.units):
                self.current_unit = 0

            self.set_current_unit(self.current_unit)
        else:
            if len(self.units) > 0:
                self.set_current_unit(0)

    '''returns set of pl_coords, matching current wm_cell; raises exception if no matching coords found'''
    def get_pl_coords_by_wm_cell(self, wm_cell):
        result = set()
        for pl_coords in self.map:
            if wm_cell == self.map[pl_coords].wm_cell:
                result.add(pl_coords)
        if len(result) > 0:
            return result
        else:
            raise Exception('Tried to get pl_coords by wm_cell - no matching pl_coords')

    def change_vision_area(self, unseen_cells, seen_cells, changed_cells):
        '''
        unseen - cells, which player can't see any more,
            list of elements like {'wm_cell' : cell_inst, 'pl_cell': string
        seen - which he couldn't see before but see now,
            list of elements like {'wm_cell' : cell_inst, 'pl_cell': string
        changed - visible cells was changed (by other players, ...)
            list of cell_inst
            '''
        if self.is_players_turn:
            # todo - fog for unseen cells
            unseen_wm_cells = set()
            for element in unseen_cells:
                unseen_wm_cells.add(element['wm_cell'])

            seen_wm_cells = set()
            for element in seen_cells:
                if element['wm_cell'] in unseen_wm_cells:
                    seen_cells.remove(element)
                else:
                    seen_wm_cells.add(element['wm_cell'])
            updated_cells = self.update_map(seen_cells)
            self.V.update_cells(updated_cells, self.map)

            changed_cells_coords = []
            for element in changed_cells:
                if element not in unseen_wm_cells and \
                                element not in seen_wm_cells:
                    for pl_coords in self.get_pl_coords_by_wm_cell(element):
                        if pl_coords not in changed_cells_coords:
                            changed_cells_coords.append({'pl_coords': pl_coords, 'wm_cell': element})
            updated_cells = self.update_map(changed_cells_coords)
            self.V.update_cells(updated_cells, self.map)
        else:
            queue = self.queue[self.change_vision_area]
            arg_list = [unseen_cells, seen_cells, changed_cells]
            if len(queue) > 1:
                raise Exception('more then 1 change_vision_area functions in player queue')
            elif len(queue) == 1:
                self.queue[self.change_vision_area][0] = helpers.summ(queue[0], arg_list)
            else:
                self.queue[self.change_vision_area] = [arg_list]

    def new_unit(self, unit_type, name, wm_cell, pl_cell, radius = 1):
        new_unit = unit_type(name, self, wm_cell, pl_cell, radius)
        self.add_unit(pl_cell, new_unit)

    def move_unit_one_step(self, direction):
        unit_index = self.current_unit
        if unit_index is not None:
            unit = self.get_unit_by_index(unit_index)
            unit.one_step(direction)

    def execute_current_unit_special_action(self):
        unit = self.get_current_user_obj()
        if unit is not None:
            if isinstance(unit.special_action, function_argument_requirer):
                result = unit.special_action.execute(unit, self)
                if isinstance(result, str):
                    self.V.message(result)
                if self.current_unit is not None and unit.action_points <= 0:
                    self.units[self.current_unit].status = 'moved'
            else:   # todo - add types
                self.V.message('No special action')
        else:
            self.V.message('No current unit')

    def get_cell_info(self, pl_cell):
        if pl_cell in self.map:
            wm_coords = self.map[pl_cell].link
            wm_cell = G.world_map.get_cell(wm_coords)
            self.V.info_window(wm_cell.get_info())

    def get_cell_for_new_unit(self, slot_type):
        '''slot_type is 'warrior', 'civilian' or 'characters' - cell slot type'''
        for cell_coords in self.map:
            wm_coords = self.map[cell_coords].link
            wm_cell = G.world_map.get_cell(wm_coords)
            if not isinstance(wm_cell, wm.worlds_end):
                if not wm_cell.sea and (wm_cell.units[slot_type] is None or slot_type == 'characters'):
                    return {'pl_cell' : cell_coords, 'wm_cell' : wm_coords}

    def message(self, text):
        if self.is_players_turn:
            self.V.message(text)
        else:
            if self.message not in self.queue:
                self.queue[self.message] = [[text]]
            else:
                self.queue[self.message].append([text])

    def __repr__(self):
        return 'player | name ' + self.name