#  WRAPPED 2D ARRAY CLASS
#  Purpose/Functionality: This class creates an array and a set of pointers.  The pointers have functions to move them around that keep them within the bounds of the array.  Also there are functions to add/remove pointers, resize the array, etc.
#  Current Status: 
#         No error handling.  
#         Currently remove pointer will screw up pointer indexes (reducing the value of the ones afterward), which may cause problems in use.  May want to change to tuple?  Maybe just be careful with use?Y!
from numpy import *
import time

class Wrapped2DArray():
#VARIABLES, with default values

    def __init__(self):
        self.arraysize = 10 # array runs from 0..arraysize -1, we assume a square array throughout
        self.pointer = [[0,0]] # list of [y,x] position pointers
        self.numpointers = 1
        self.ThisArray = zeros((self.arraysize,self.arraysize), float) # the actual array, 
                                                         #default is all elements zero



########  METHODS (AKA FUNCTIONS)  ########


#MOVING POINTERS AROUNDS:

#moves pointer toward top one index
    def move_up(self, pointer_index): 
        if self.pointer[pointer_index][0] > 0: #for bulk behavior
            self.pointer[pointer_index][0] -= 1
        else: # on edge, wrap to bottom
            self.pointer[pointer_index][0] = self.arraysize -1         
        return 

#moves pointer toward bottom one index
    def move_down(self, pointer_index):
        if self.pointer[pointer_index][0] < self.arraysize -1: #for bulk behavior
            self.pointer[pointer_index][0] += 1
        else: # on edge, wrap to top
            self.pointer[pointer_index][0] = 0    
        return 

#moves pointer toward right one index  
    def move_right(self, pointer_index):
        if self.pointer[pointer_index][1] < self.arraysize -1: #for bulk behavior
            self.pointer[pointer_index][1] += 1
        else: # on right edge, wrap to left edge
            self.pointer[pointer_index][1] = 0    
        return 

#moves pointer toward left one index
    def move_left(self, pointer_index): 
        if self.pointer[pointer_index][1] > 0: #for bulk behavior
            self.pointer[pointer_index][1] -= 1
        else: # on left edge, wrap to right edge
            self.pointer[pointer_index][1] = self.arraysize -1         
        return 

#LOOKING AROUND NEIGHBORHOOD
    def look_up(self, pointer_index):
        if self.pointer[pointer_index][0] > 0: #for bulk behavior
            p = [self.pointer[pointer_index][0] - 1, self.pointer[pointer_index][1] ]
        else: # on edge, wrap to bottom
            p = [self.arraysize - 1, self.pointer[pointer_index][1] ]
        return p

    def look_down(self, pointer_index):
        if self.pointer[pointer_index][0] < self.arraysize -1: #for bulk behavior
            p = [self.pointer[pointer_index][0] + 1, self.pointer[pointer_index][1] ]
        else: # on edge, wrap to top
            p = [0, self.pointer[pointer_index][1] ]    
        return p

    def look_right(self, pointer_index):
        if self.pointer[pointer_index][1] < self.arraysize -1: #for bulk behavior
            p = [self.pointer[pointer_index][0], self.pointer[pointer_index][1] + 1]
        else: # on right edge, wrap to left edge
            p = [self.pointer[pointer_index][0], 0]
        return p

    def look_left(self, pointer_index): 
        if self.pointer[pointer_index][1] > 0: #for bulk behavior
            p = [self.pointer[pointer_index][0], self.pointer[pointer_index][1] - 1]
        else: # on left edge, wrap to right edge
            p = [self.pointer[pointer_index][0], self.arraysize -1]
        return p

#moves pointer (pointer_index) down by num_down and right by num_right, negatives move opposite direction.  Wrapped movement.
    def move(self, pointer_index, num_down, num_right):
        if num_down > 0:
            for i in xrange(0, num_down):
                self.move_down(pointer_index)
        elif num_down < 0:
            for i in xrange(0, -1*num_down):
                self.move_up(pointer_index)
        if num_right > 0:
            for i in xrange(0, num_right):
                self.move_right(pointer_index)
        elif num_right < 0:
            for i in xrange(0, -1*num_right):
                self.move_left(pointer_index)
	# note nothing happens if num_down==num_right==0
        return

#takes a pointer at pointer_index and makes sure it's actually pointing to something in the the array
    def move_in_range(self,pointer_index):
        self.pointer[pointer_index][0] = mod(self.pointer[pointer_index][0], self.arraysize)
        self.pointer[pointer_index][1] = mod(self.pointer[pointer_index][1], self.arraysize) 
        return self.pointer[pointer_index]

#moves the pointer at pointer index over one, as a book reads.  Returns true if within array.  Doesn't move and retruns false if tries to move out of array.
    def next(self, pointer_index):
        max_array_index = self.arraysize -1 #since arraysize is number of objects, and index begins at zero 
        if (self.pointer[pointer_index][0] >= max_array_index) and (self.pointer[pointer_index][1] >= max_array_index):
            self.pointer[pointer_index] = [0,0]
            return False #outside array
        if self.pointer[pointer_index][1] == max_array_index:
            self.move_down(pointer_index) #if at last entry in line, move to next line
        self.move_right(pointer_index)
        return True

#ADJUSTING ARRAY/POINTERS

#makes a new array of zeros of self.arraysize, removes pointers, or both
    def clear(self,type = 'all'):
        if type in ['pointers', 'pointer']:
            self.pointer = [[0,0]]
            self.numpointers = 1 
        elif type in ['array', 'arrays']:
            self.ThisArray = zeros((self.arraysize,self.arraysize), dtype = float)
        elif type in ['all', 'All']:
            self.ThisArray = zeros((self.arraysize,self.arraysize), dtype = float)         
            self.pointer = [[0,0]]
            self.numpointers = 1
        return

#makes a new array of zeros of new_size
    def resize_array(self, new_size): #WARNING: this CLEARS the array!
        self.arraysize = new_size
        self.clear('array')
        return

#possible idea.. have resize_array make a copy of old array and return, in case we want to keep the old one around?
#copy_original = self.ThisArray
#return copy_original

#adds a pointer, default at [0,0] or at whatever you give it and returns the index
    def add_pointer(self, pointer = [0,0]):
        self.pointer.append(pointer)
        self.move_in_range(self.numpointers) # forces new pointer to point inside array
        self.numpointers += 1
        return self.numpointers-1 # INDEX of new pointer
#removes a pointer at pointer index and returns the pointer it deleted
    def remove_pointer(self, pointer_index):
        temp = self.pointer.pop(pointer_index)
        self.numpointers -=1
        return temp

#DATA TRANSACTIONS

#returns the value of the array at the pointer indexed by pointer_index
    def fetch(self, pointer_index):
        self.move_in_range(pointer_index)
        temp = self.pointer[pointer_index]
        return self.ThisArray[temp[0],temp[1]]

#sets the value of ThisArray at the pointer listed, returns the pointer it set the value at
    def set(self, pointer_index, value):
        temp = self.move_in_range(pointer_index)
        self.ThisArray[temp[0],temp[1]] = value
        #print temp
        return

#OUTPUT

#Print the 2D array to a file
    def save(self,fname):
        f = open(fname,'a')    #append to file

        for i in range(0,self.arraysize):
            for j in range(0,self.arraysize):
                f.write(str(self.ThisArray[i,j]))
                f.write(' ')
            f.write('\n')
        f.close()
        return fname

    def load(self, fname):
        line_list =[0]
        f = open(fname)
#        maxLen=0
        numLines=0

        for line in f:
                numLines +=1
        f.close()

        arrList = []
        shaper = (self.arraysize,self.arraysize)

        f = open(fname)
        for i in range(0,numlines/self.arraysize):
            arr = zeros(shaper,float)
            k = 0
            for line in f:
                for j in range(0,self.arraysize):
                    arr[k,j] = float( line.split()[j] )
                k+=1
            arrList.append(arr)

        f.close()
        return arrList


