#!/usr/bin/env python
# encoding: utf-8
# @project : Assignment3
# @author: Zekun Zhang
# @contact: zekunzhang.1996@gmail.com
# @file: Moving.py
# @time: 2019-11-11 18:53:10
import numpy as np


class MovingExplorer:
    def __init__(self, origin_map):
        self.origin_map = origin_map.origin_map  # origin map which contains 4 different terrain type from 0 to 3
        self.row, self.col = np.shape(self.origin_map)
        self.mappings = origin_map.mappings  # sets for different p with different terrain type

        self.rand = np.random.randint(0, self.row * self.col)  # rand seed
        self.target = [self.rand // self.col, self.rand % self.col]  # random target generated by rand seed
        self.dir_arr = ([0, 1], [1, 0], [0, -1], [-1, 0])
        self.tracker = -1

        self.p_map = np.ones((self.row, self.col)) / (
                self.row * self.col)  # possibility map for search under moving
        self.p_map_temp = self.p_map.copy()

        self.search_count = 0
        self.search_action = 0

    def target_moving(self):
        move = list(self.dir_arr).copy()
        while True:
            random = np.random.choice(len(move))
            temp = [self.target[0], self.target[1]]
            temp[0] += move[random][0]
            temp[1] += move[random][1]
            if 0 <= temp[1] < self.col and 0 <= temp[0] < self.row:
                self.target = temp
                break
            else:
                del move[random]

    def get_target_terrain(self):
        list = [0, 1, 2, 3]
        list.remove(self.origin_map[tuple(self.target)])
        return list[np.random.choice(len(list))]

    def map_update(self, terrain):
        self.p_map_temp = self.p_map.copy()
        # self.p_map_temp[self.origin_map == terrain] = 0

    def check(self, grid):  # check one grid failure or success
        if self.target != list(grid):
            return False
        else:
            random = np.random.random()  # generate a random p from 0 to 1
            return random >= self.mappings[self.origin_map[grid]]  # check return a failure or success

    def grid_choice(self, pre_grid):
        arr4 = np.argwhere((self.origin_map == 3) & (self.p_map_temp == np.max(self.p_map_temp)))
        arr3 = np.argwhere((self.origin_map == 2) & (self.p_map_temp == np.max(self.p_map_temp)))
        arr2 = np.argwhere((self.origin_map == 1) & (self.p_map_temp == np.max(self.p_map_temp)))
        arr1 = np.argwhere((self.origin_map == 0) & (self.p_map_temp == np.max(self.p_map_temp)))
        if arr1.size > 0:
            candidates = arr1
        elif arr2.size > 0:
            candidates = arr2
        elif arr3.size > 0:
            candidates = arr3
        else:
            candidates = arr4

        if pre_grid is None:
            return tuple(candidates[np.random.choice(len(candidates))])
        else:
            # min_grids = np.argwhere(
            #     abs(candidates - pre_grid).sum(axis=1) - np.min(
            #         abs(candidates - pre_grid).sum(axis=1)) < 10).flatten().tolist()
            min_grid = np.argmin(abs(candidates - pre_grid).sum(axis=1))
            # return tuple(candidates[np.random.choice(min_grids)])
            return tuple(candidates[min_grid])

    def get_distance(self, p, q):  # get distance between two grids
        return abs(p[0] - q[0]) + abs(p[1] - q[1])

    def search(self):
        while True:
            # print(str(self.search_count) + ":" + str(self.search_action))
            self.search_count += 1
            self.tracker = self.get_target_terrain()
            self.map_update(terrain=self.tracker)
            grid = self.grid_choice(None if self.search_count == 1 else pre_grid)
            self.search_action += self.get_distance(grid, grid if self.search_count == 1 else pre_grid)
            if self.check(grid):
                break
            else:
                observation = 1 - self.p_map[grid] + self.p_map[grid] * self.mappings[self.origin_map[grid]]
                self.p_map[grid] *= self.mappings[self.origin_map[grid]]
            self.p_map /= observation
            pre_grid = grid
            self.target_moving()
        # print("Search times: " + str(self.search_one_count1))
        return self.search_count + self.search_action, self.search_count
