from Wrapped2DArray import *


#This class is designed to provide statistics about "blobs" or high mass regions on the automata.  
#It lists all the blobs in the Blob_List (each item being the blob itself).
#It contains the following statistics/data: size of blob, perimeter of blob, the ratio of the two, and their averages.  Also the number of blobs.
#It also has a map of the blob regions, where the blobs are value 2 in a background of 0s.  This is called "Full_Map"
#This tool requires that you pass it a Wrapped2DArray to analyze.
class Blob_Tool():

#Default constructor, initializes the variables in the the Blob_Tool Class
    def __init__(self):
        self.Full_Map = Wrapped2DArray()
        self.Blob_List = []
        self.num_blob = 0
        self.full_amount = .8
        self.perimeters = []
        self.sizes = []
        self.average_size = 0
        self.average_perimeter = 0
        self.sizes_over_perimeters = []
        self.average_size_over_perimiters = 0

#Sizes Full_Map to the same size as ext_ref
#Input: ext_ref: Wrapped2DArray
    def Initialize_Full_Map(self,ext_ref):
        new_size = ext_ref.arraysize
        self.Full_Map.resize_array(new_size)
        return

#Goes through the ext_ref and finds all blobs, saving Full_Map, num_blobs, and Blob_List (all the blobs)
#Input: ext_ref: A Wrapped2DArray to find the blobs in
    def Blob_Search(self, ext_ref):
        Iterating = True
        pointer = ext_ref.add_pointer([0,0])
        from copy import deepcopy
        while Iterating:
            for i in [0,1]:
                self.Full_Map.pointer[0][i] = ext_ref.pointer[pointer][i]  #puts full map pointer at location of ext ref
            if (ext_ref.fetch(pointer) > self.full_amount) and (self.Full_Map.fetch(0) != 2):
                self.Blob_List.append(Blob()) #makes blob
                self.Blob_List[self.num_blob].full_amount = self.full_amount
                templist = [self.Full_Map.pointer[0][0],self.Full_Map.pointer[0][1]]
                self.Blob_List[self.num_blob].Initialize(ext_ref, deepcopy(templist))
                for k in xrange(0, self.Full_Map.arraysize):
                    for l in xrange(0,self.Full_Map.arraysize):
                        self.Full_Map.ThisArray[k][l] += self.Blob_List[self.num_blob].map.ThisArray[k][l] #adds map to Full Map
                self.num_blob +=1
            Iterating = ext_ref.next(pointer)
        ext_ref.remove_pointer(pointer)
        return

#Once the blobs are found, this function will find the statistical properties of them.  They are:
#Average size, average parameter, average ratio, and the lists of the individual values
    def Get_Parameters(self):
        if self.num_blob != 0:
            for i in xrange(0,self.num_blob):
                self.sizes.append(self.Blob_List[i].size)
                self.perimeters.append(self.Blob_List[i].perimeter)
                self.sizes_over_perimeters.append(float(self.Blob_List[i].size)/(float(self.Blob_List[i].perimeter)))                        
                self.average_size = add.reduce(self.sizes)/float(self.num_blob)
                self.average_perimeter = add.reduce(self.perimeters)/float(self.num_blob)
                self.average_sizes_over_perimeters = add.reduce(self.sizes_over_perimeters)/float(self.num_blob)
                
#Runs all the basic functions of Blob_Tool, placed together for brevity and organization.
    def Initialize(self, ext_ref):
        self.Initialize_Full_Map(ext_ref)
        self.Blob_Search(ext_ref)
        self.Get_Parameters()

#This class creates an object called a "Blob" or high mass region of the automata.  
#It can find the shape, size, and perimeter of the blob and stores these
#The "map" is a Wrapped2DArray with 2's covering the blob location at 0's elsewhere.
#The reference is an internal copy of the Wrapped2DArray it wants to analyze
#This class requires a external reference (Wrapped2DArray) to analyze and a starting location (pointer on the internal reference Wrapped2DArray) that is within a blob.
class Blob():
#Default constructor containing the variables of the class
    def __init__(self):
        self.map = Wrapped2DArray()
        self.reference = Wrapped2DArray()
        self.neighborhood = [0,0,0,0]
        self.full_amount = .8 #the minimum mass to considered part of a "blob"
        self.numN = 4 #number of neighbors in the automata (adjencent cells to look into)
        self.size = 0
        self.perimeter = 0
        self.marked = False
#Resizes map and reference to proper size then copys the external reference to the internal
    def Set_Reference(self,ext_ref):
        new_size = ext_ref.arraysize
        self.reference.resize_array(new_size)
        self.map.resize_array(new_size)
        Pointer = ext_ref.add_pointer([0,0])
        iterating = True
        while iterating:
            for i in [0,1]:
                self.reference.pointer[0][i] = ext_ref.pointer[Pointer][i]
            self.reference.set(0, ext_ref.fetch(Pointer))
            iterating = ext_ref.next(Pointer)
        ext_ref.remove_pointer(Pointer)
        return
#Given a starting locatin (start being a pointer [row,col] in reference) it will find all other high mass cells that share a boundry with it, counting them (setting "size"
    def Flesh_Out(self,start):
        temp = self.reference.add_pointer(start)	
        self.mark(temp)
        self.reference.remove_pointer(temp)
        Iterating = True
        while Iterating:
            if self.map.fetch(0)==1: # 1 means there is mass there, but neighbors haven't been investigated
                self.find_neighbors(0)
                self.map.set(0,2) # marks as already found neighbor
                for i in xrange(0,len(self.neighborhood)):
                    temp = self.reference.add_pointer(self.map.pointer[self.neighborhood[i]])
                    self.mark(temp) #marks neighbors if above full amount
                    self.reference.remove_pointer(temp)
                self.forget_neighbors()    
            not_end = self.map.next(0)    
            Iterating = (self.marked or not_end) #ends if it both hasn't marked this run through and at end of array
            if not_end == False:
                self.marked = False
        return self.map

#Same as in automata, basically.  This function makes four pointers in map that are above, below, and to each side (wrapped) one cell relative to position at pointer_index and puts the pointer indexes in the neighborhood list
    def find_neighbors(self, pointer_index):
        m = self.map
        p = m.add_pointer	# adds a new pointer for each neighbor
        self.neighborhood = []
        self.neighborhood.append( p(m.look_up(pointer_index)) )	#a
        self.neighborhood.append( p(m.look_left(pointer_index)) )	#b
        self.neighborhood.append( p(m.look_right(pointer_index)) )	#d
        self.neighborhood.append( p(m.look_down(pointer_index)) )	#e
        return	# now self.neighborhood = [a,b,c,d,e]        

#Removes the pointers in the neighborhood list
    def forget_neighbors(self):
        for i in range(self.numN-1,-1,-1): #4,3,2,1,0
            self.map.remove_pointer(self.neighborhood[i])
            self.neighborhood.pop()
        return # now self.neighborhood = []

#Takes in a pointer index to reference array and marks if > full amount and not marked already
    def mark(self, pointer_index):
        temp = self.map.add_pointer(self.reference.pointer[pointer_index])
        if (self.reference.fetch(pointer_index) > self.full_amount)  and (self.map.fetch(temp) ==0):
            self.marked = True
            self.map.set(temp, 1)
            self.size += 1
        self.map.remove_pointer(temp)
        return

#This function finds the perimeter of the blob in map
    def Find_Perimeter(self):
        temp = self.map.add_pointer([0,0])
        Iterating = True
        perimeter = 0
        while Iterating:
            sides = 0
            if self.map.fetch(temp)==2:
                self.find_neighbors(temp)
                for i in xrange(0,len(self.neighborhood)):
                    if self.map.fetch(self.neighborhood[i])==0: #empty neighbors mean its that's one unit of perimeter
                        sides +=1
                self.forget_neighbors()
            Iterating = self.map.next(temp)   
            perimeter += sides
        self.perimeter = perimeter
        self.map.remove_pointer(temp)
        return perimeter

#Runs all the basic functions in Blob.  Made for brevity and organization.
#Inputs: ext_ref: a Wrapped2DArray to find blobs in.  Start: a pointer ([row,col]) in internal reference to a location within the blob  
    def Initialize(self, ext_ref, start):
        self.Set_Reference(ext_ref)
        self.Flesh_Out(start)
        self.Find_Perimeter()
        return
