# Bismillah-ir-Rahman-ir-Rahim
import numpy as np
import random
import math
import sys
import matplotlib.pyplot as plt
import itertools


class DendricCells():
    p = 0.125  # Fixed value to make decision for moving Dendric Cell
    density_dc = 0.01  # No of dc in an unit square
    rad = 2  # Petrolling Radius of Dendric Cell
    #colormap = np.array(['m', 'y', 'b', 'w'])

    def __init__(self, arena_x, arena_y):
        self.arena_x = arena_x  # x-axis of arena
        self.arena_y = arena_y  # y-axis of arena
        self.cell_x = list()
        self.cell_y = list()
        self.num_dc = list()
        self.dcmovedx = list()  # Moved Position of Dendric Cell Along X-Axis
        self.dcmovedy = list()  # Moved Position of Dendric Cell Along Y-Axis
        self.dc_radius = list()  # Euclidean Distance by Dendric Cell in its 4-neighbor
        self.dc_loc = list()  # Location of Dendric Cells in pairs
        self.bac_detected = list()  # location of those DCs which intersect with bacteria
        self.maturity_state = np.ones(self.num_dc, dtype=int)  # Maturity status of each DC in array form
        self.pamp_signal = np.zeros(self.num_dc)   # Maturity State due pathogen Associated Molecular patterns
        self.dc_detected_bac_index = list()
        self.danger_signal = np.zeros(self.num_dc)  # Maturity due to environment sensors
        self.safe_signal = np.zeros(self.num_dc)  # safe signal by body cells due to environment effect
        self.danger_pre_val = np.zeros(self.num_dc)  # To save the previous value of danger signal generated by DC
        self.dc_output = np.zeros(self.num_dc) # Complete output of DC Maturity
        self.dc_help_signal = np.zeros([arena_x, arena_y])
        self.get_bac_pattern = list()
        self.dc_nei = []

    def placement(self, density_dc):
        pad_x = max(round(self.arena_x * (0.04)), 2)  # Padding of 4%
        pad_y = max(round(self.arena_y * (0.04)), 2)
        lenx = (self.arena_x - pad_x - 1) - (pad_x)
        leny = (self.arena_y - pad_y - 1) - (pad_y)
        n_dc = int(density_dc * (self.arena_x * self.arena_y))
        # print('No of DC', n_dc)
        if lenx >= leny:
            w = lenx
            h = leny
        else:
            w = leny
            h = lenx
        nx = math.sqrt((w * n_dc / h) + ((w - h) / (2 * h)) ** 2)
        ny = n_dc / nx
        x_arr = np.linspace(pad_x, self.arena_x - pad_x - 1, round(nx), dtype=int)
        y_arr = np.linspace(pad_x, self.arena_y - pad_y - 1, round(ny), dtype=int)
        pos = list(itertools.product(x_arr, y_arr))  # Position in the form of oerder pair
        self.cell_x = [e[0] for e in pos]
        self.cell_y = [e[1] for e in pos]
        self.num_dc = len(x_arr) * len(y_arr)
        self.maturity_state = np.ones(self.num_dc, dtype=int)
        self.danger_pre_val = np.zeros(self.num_dc)
        self.danger_signal = np.zeros(self.num_dc)
        self.safe_signal = np.zeros(self.num_dc)
        self.pamp_signal = np.zeros(self.num_dc)
        self.dc_output = np.zeros(self.num_dc)
        self.get_bac_pattern = [set() for _ in range(self.num_dc)]
        self.dc_nei = np.zeros((self.num_dc*4*2), dtype=int).reshape(self.num_dc, 4, 2)
        return self.num_dc, self.cell_x, self.cell_y

    def scanning(self):  # DC cell move on its r = 1 neighbour
        self.dc_loc.clear()
        self.dc_nei = []
        self.dcmovedx = list(self.cell_x)
        self.dcmovedy = list(self.cell_y)
        for i in range(self.num_dc):
            r = random.random()  # random number to move DC
            #print(r)
            if r < DendricCells.p and self.dc_output[i] < 0.7:  # DC move Upper-Left
                self.dcmovedx[i] = self.dcmovedx[i] - 1
                self.dcmovedy[i] = self.dcmovedy[i] + 1
                #print('Upper-Left')
            elif (DendricCells.p <= r < 2 * DendricCells.p) and self.maturity_state[i] < 3:  # DC move UP
                self.dcmovedy[i] = self.dcmovedy[i] + 1
                #print('Up')
            elif (2 * DendricCells.p <= r < 3 * DendricCells.p) and self.maturity_state[i] < 3:  # DC move Upper-Right
                self.dcmovedx[i] = self.dcmovedx[i] + 1
                self.dcmovedy[i] = self.dcmovedy[i] + 1
                #print('Upper-Right')
            elif (3 * DendricCells.p <= r < 4 * DendricCells.p) and self.maturity_state[i] < 3:  # DC move Right
                self.dcmovedx[i] = self.dcmovedx[i] + 1
                #print('Right')
            elif (4 * DendricCells.p <= r < 5 * DendricCells.p) and self.maturity_state[i] < 3:  # DC move Down-Rght
                self.dcmovedx[i] = self.dcmovedx[i] + 1
                self.dcmovedy[i] = self.dcmovedy[i] - 1
                #print('Down-Rght')
            elif (5 * DendricCells.p <= r < 6 * DendricCells.p) and self.maturity_state[i] < 3:  # DC move Down
                self.dcmovedy[i] = self.dcmovedy[i] - 1
                #print('Down')
            elif (6 * DendricCells.p <= r < 7 * DendricCells.p) and self.maturity_state[i] < 3:  # DC move Left-Down
                self.dcmovedx[i] = self.dcmovedx[i] - 1
                self.dcmovedy[i] = self.dcmovedy[i] - 1
                #print('Down-Left')
            elif (7 * DendricCells.p <= r < 8 * DendricCells.p) and self.maturity_state[i] < 3:  # DC move Left
                self.dcmovedx[i] = self.dcmovedx[i] - 1
                #print('Left')
            self.dc_loc.insert(i, [self.dcmovedx[i], self.dcmovedy[i]])
        # neighboring location of DCs
        for idx, dc in enumerate(self.dc_loc):  # find neighbors for each dc
            nei = [[dc[0] - 1, dc[1]], [dc[0] + 1, dc[1]], [dc[0], dc[1] - 1], [dc[0], dc[1] + 1]]
            self.dc_nei += nei
            # print(self.dcmovedx)
        # print(self.dcmovedy)
        return self.dcmovedx, self.dcmovedy, self.dc_loc  # Updated Location of DC

    def arrayupdation(self):
        colormap = np.array(['m', 'y', 'b', 'w'])
        #print(self.maturity_state)
        plt.scatter(self.dcmovedy, self.dcmovedx, marker='.', c=colormap[self.maturity_state])

    def movetocenter(self):  # Finding City Block Distance and Take decision on that Basis
        for i in range(self.num_dc):
            r = (abs(self.dcmovedx[i] - self.cell_x[i]) + abs(self.dcmovedy[i] - self.cell_y[i]))
            if r == 2 and self.dcmovedx[i] < self.cell_x[i] and self.dcmovedy[i] > self.cell_y[i]:  # DC Present on Upper-Left
                self.dc_radius.append(r)
                self.dcmovedx[i] += 1
                self.dcmovedy[i] -= 1
            elif r == 1 and self.dcmovedy[i] > self.cell_y[i]:  # DC Present on up
                self.dc_radius.append(r)
                self.dcmovedy[i] -= 1
            elif r == 2 and self.dcmovedx[i] > self.cell_x[i] and self.dcmovedy[i] > self.cell_y[i]:  # DC Present on Upper-Right
                self.dc_radius.append(r)
                self.dcmovedx[i] -= 1
                self.dcmovedy[i] -= 1
            elif r == 1 and self.dcmovedx[i] > self.cell_x[i]:  # DC Present on Right Side
                self.dc_radius.append(r)
                self.dcmovedx[i] -= 1
            elif r == 2 and self.dcmovedx[i] > self.cell_x[i] and self.dcmovedy[i] < self.cell_y[i]:  # DC Present on Lower-Right Side
                self.dc_radius.append(r)
                self.dcmovedx[i] -= 1
                self.dcmovedy[i] += 1
            elif r == 1 and self.dcmovedy[i] < self.cell_x[i]:  # DC Present on Down Side
                self.dc_radius.append(r)
                self.dcmovedy[i] += 1
            elif r == 2 and self.dcmovedx[i] < self.cell_x[i] and self.dcmovedy[i] < self.cell_y[i]:  # DC Present on Lower-Right Side
                self.dc_radius.append(r)
                self.dcmovedx[i] += 1
                self.dcmovedy[i] += 1
            elif r == 1 and self.dcmovedx[i] < self.cell_x[i]:  # DC Present on Right Side
                self.dc_radius.append(r)
                self.dcmovedx[i] += 1

    def detect_pathogen(self, bac_loc, bac_pattern):  # Update the maturity value for each DC On detecting Bacteria
        for bac in bac_loc:
            #print('Try to detect bacteria')
            #print(bac_loc)
            #print('self.dc_loc = ', self.dc_loc)
            #print('neighbor', self.dc_nei)
            if bac in self.dc_loc:  # Condition if Bacteria found at DC center
                dc_num = self.dc_loc.index(bac)  # Index of DC who detected the bacterium
                #bac_detected = self.dc_loc[idx]  # Append list of detected bacteria location
                #print('DC number {0} and----------------------------- pathogen location is {1}'.format(idx, bac_detected))
                self.get_bac_pattern[dc_num].add(bac_pattern)
                if self.pamp_signal[dc_num] < 0.1:
                    self.pamp_signal[dc_num] += 0.05
            elif bac in self.dc_nei: # Condition for bac in the vicinity of DC
                #print('bacteria', bac)
                dc_num = DendricCells.fitness(self.dc_loc, bac)
                self.get_bac_pattern[dc_num].add(bac_pattern)
                # self.get_bac_pattern[dc_num] - self.tcr_find_bac
                if self.pamp_signal[dc_num] < 0.1:
                    self.pamp_signal[dc_num] += 0.025
                    #print('Detected in the vicinity of -------------------------DC ', dc_num)
        return self.pamp_signal

    def maturity_danger(self, chemotaxis):  # Danger Associated Molecular Patterns D (Mean Kernel function)
        #maturity_update = np.zeros(self.num_dc)
        maturity_value = np.zeros(self.num_dc)
        #print('Danger of dc', self.danger_signal)
        for i in range(self.num_dc):
            x = self.cell_x[i]
            y = self.cell_y[i]
            val = round((1/9)*(chemotaxis[x-1][y+1] + chemotaxis[x][y+1] + chemotaxis[x+1][y+1] + chemotaxis[x-1][y] + chemotaxis[x][y] + chemotaxis[x+1][y] + chemotaxis[x - 1][y - 1] + chemotaxis[x][y - 1] + chemotaxis[x + 1][y - 1]),2)
            #print('Each value captured by DC', val)
            maturity_value[i] = val
        #print('maturity update', maturity_value)
        max_maturity = np.amax(maturity_value)
        if max_maturity <= 0.001:
            max_maturity = 0.1
        a = 1/(10 * max_maturity)
        maturity_value = (a * maturity_value)
        m = np.mean(maturity_value)  # Mean
        s = np.std(maturity_value)  # Standard deviation
        for idx, dc_val in enumerate(maturity_value):
            if dc_val >= m + s:
                self.danger_signal[idx] = self.danger_signal[idx] + 0.01
            elif dc_val <= m - s:
                self.danger_signal[idx] = self.danger_signal[idx] - 0.01
            else:
                self.danger_signal[idx] = self.danger_signal[idx]
        #print('Danger Maturity', self.danger_signal)
        return self.danger_signal

    def maturity_danger1(self, chemotaxis):  # Danger Associated Molecular Patterns D (Mean Kernel function)
        current_val = np.zeros(self.num_dc)
        a = 1
        for i in range(self.num_dc):
            x = self.cell_x[i]
            y = self.cell_y[i]
            #print('X', x)
            #print('Y', y)
            #print('valy', self.dcmovedy[i])
            #print('valx', self.dcmovedx[i])
            #print('valcur', self.danger_pre_val[i])
            current_val[i] = round((1/9)*(chemotaxis[x-1][y+1] + chemotaxis[x][y+1] + chemotaxis[x+1][y+1] + chemotaxis[x-1][y] + chemotaxis[x][y] + chemotaxis[x+1][y] + chemotaxis[x - 1][y - 1] + chemotaxis[x][y - 1] + chemotaxis[x + 1][y - 1]),2)
            diff = current_val[i] - self.danger_pre_val[i]
            if diff > 0.2 and self.danger_signal[i] < 0.1 :  # Addition when DC see positive change
                self.danger_signal[i] += 0.012/max(1,self.maturity_state[i])
                self.safe_signal[i] -= 0.012/max(1,self.maturity_state[i])
                #print('Condition1')
            elif ((diff < -0.1 and self.danger_signal[i] > 0.04) or (chemotaxis[x][y] == 0 and self.danger_signal[i] > 0.01)) and self.maturity_state[i] < 3:  # Subtraction when DC observe negative change
                self.danger_signal[i] -= 0.012
                self.safe_signal[i] += 0.012
                #print('Condition2')
            else:  # In the case of no change
                continue
            #print('Danger Matuity = ', self.danger_signal)
        #print('DC value', current_val)
        self.danger_pre_val = current_val
        return self.danger_signal

    def commulative_maturity(self, w): # Combine Maturity and Output of danger signal
        self.dc_output = np.round(w[0] * self.pamp_signal + w[1] * self.danger_signal - w[2] * self.safe_signal , 2)
        for idx, value in enumerate(self.dc_output):
            if value >= 1.0:  # Condition to overcome saturation of DC
                #print('Begining of a New.................................................DC')
                self.dc_output[idx] = 0
                self.danger_signal[idx] = 0
                self.danger_pre_val[idx] = 0
                self.pamp_signal[idx] = 0
                self.safe_signal[idx] = 0
                self.get_bac_pattern[idx].clear()
            elif 0.7 <= value <= 1.0:  # Generation of APCs
                continue
                #print('DC number {0} is Fully matured'.format(idx))
        #print('Total maturity', maturity)
        self.maturity_state = [DendricCells.value_dc(element) for element in self.dc_output]
        #self.previou_maturity_state = self.maturity_state
        #danger = sum([self.previou_maturity_state[i] - self.maturity_state[i] for i in range(len(self.maturity_state))])
        return self.maturity_state, self.dc_output

    def help_signal(self):
        self.dc_help_signal = np.zeros([self.arena_x, self.arena_y])
        for dc_num, dc_state in enumerate(self.maturity_state):
            if dc_state == 3:
                DendricCells.chessboard_distance(self.arena_x, self.arena_y, self.dc_help_signal, 5, self.dc_loc[dc_num])

    @staticmethod
    def value_dc(x):
        if 0 <= x < 0.3:
            return int(1)  # Immature
        elif 0.3 <= x < 0.7:
            return int(2)  # Semi-mature
        elif 0.7 <= x < 1.0:
            return int(3)  # Fully mature
        else:
            return int(0)  # Beginning of New DC

    @staticmethod
    def chessboard_distance(arena_x, arena_y, help_matrix, region, rob_loc):
        nei_x = np.array(range(max(0, rob_loc[0] - region), min(arena_x, rob_loc[0] + region + 1)))
        nei_y = np.array(range(max(0, rob_loc[1] - region), min(arena_y, rob_loc[1] + region + 1)))
        nei_loc = list(itertools.product(nei_x, nei_y))
        # print(nei_loc)
        for single_nei in nei_loc:
            # print(single_nei)
            help_matrix[single_nei[0]][single_nei[1]] = min(
                [region - abs(single_nei[0] - rob_loc[0]), region - abs(single_nei[1] - rob_loc[1])]) * 0.1
        max_value = np.amax(help_matrix)
        help_matrix = help_matrix / (2 * max_value)
        # print(arena)
        return help_matrix, nei_loc

    @staticmethod
    def fitness1(dc_locat, detected):  # For the calculation of fitness for each particle
        diff = np.array(detected) - np.array(dc_locat)
        summa = (diff * diff)
        distance = np.sqrt(summa.sum(axis=1))
        print(type(distance))
        return np.array(distance)

    @staticmethod
    def fitness(cells_loc, target):  # For the detection of nearby pathogens
        fitness = list()
        for i, cell_loc in enumerate(cells_loc):
            fitness.append(int(math.sqrt(((target[0] - cell_loc[0]) ** 2) + ((target[1] - cell_loc[1]) ** 2))))
        idx = fitness.index(1)
        return idx


# Code for testing the Dendric Cell class
'''for x in range(8):
    if x == 0:
        a = DendricCells(80, 60)
        im, DCx, DCy = a.placement(0.01)
    else:
        dcx, dcy, dcloc = a.scanning()
        plt.title('Dendric Cell Movement in its r = 1 neighborhood ' + str(x))
        plt.xlabel('DCX')
        plt.ylabel('DCY')
        plt.plot(dcx, dcy, 'r.')
        plt.show()
        a.movetocenter()'''






































