'''
Created on Oct 29, 2014

In <demos.py> :
    The file contains subclasses inherited from the original base class Demo, each subclass
    is specified to generate arrays or calculate results according to the requirements of 
    the corresponding questions

@author: luchristopher
'''
from demo import *
from itertool import *
from matplotlib import *

import sys

class DemoOne(Demo):
    '''
    In class DemoOne:
    >>>
    Methods:
        __rowGenerationDemo() : solve question 1-a
        __columnGenerationDemo() : solve question 1-b
        __areaGenerationDemo() : solve question 1-c
        __boundedValueDemo() :   solve question 1-d
    '''

    def __init__(self):
        '''
        Constructor : generate an 5 by 3 array as described
        '''
        self._the_array = np.zeros((5,3),dtype = np.int64)
        for i in range(5):
            for j in range(3):
                self._the_array[i,j] = i+1+5*j
    
    def __rowGenerationDemo(self):
        return self._the_array[(1,3),:].copy() #copy() was used to make sure the returned array is a new array rather than a view of the original
    
    def __columnGenerationDemo(self):
        return self._the_array[:,1].reshape(5,1).copy() #reshape() was used to maintain the elements as a column
    
    def __areaGenerationDemo(self):
        return self._the_array[1:4,0:3].copy()
    
    def __boundedValueDemo(self):
        return self._the_array[np.logical_and(self._the_array >=3, self._the_array <= 11)]
    
    def demoShow(self):
        print '------------------QUESTION 1-----------------------\n'
        print 'The generated array is shown below:'
        self._displayArrayDemo()
        print 'The answer to 1-a is:'
        print self.__rowGenerationDemo()
        print 'The answer to 1-b is:'
        print self.__columnGenerationDemo()
        print 'The answer to 1-c is: (Boundaries are included)'
        print self.__areaGenerationDemo()
        print 'The answer to 1-d is: (Boundary values are included)'
        print self.__boundedValueDemo()
        print '\n'


class DemoTwo(Demo):
    '''
    In class DemoTwo:
    >>>
    Methods:
        __divideElementwiseDemo() : solve question 2 using the broadcast regime
    '''

    def __init__(self):
        '''
        Constructor
        '''
        self._the_array = np.arange(25).reshape(5, 5)   #_the_array here stands for a
    
    def __divideElementwiseDemo(self):
        b = np.array([1., 5, 10, 15, 20])
        return self._the_array/b.reshape(5,1) #reshape the b array to use the broadcast regime on each column
    
    def demoShow(self):
        print '------------------QUESTION 2-----------------------\n'
        print 'The result for question 2 is:'
        print self.__divideElementwiseDemo()
        print '\n'        
        

class DemoThree(Demo):
    '''
    In class DemoThree:
    >>>
    Methods:
        __pickTheClosestDemo() : Picking the values that are closest to 0.5 in each row without using more convenient numpy argmin() 
                                and absolute() method instead of abs() and argsort()
        __pickTheClosestDemo2() : Do the same task using abs() and argsort()
    '''
    
    def __init__(self):
        self._the_array = np.random.rand(10,3)  #initialized as a random 10*3 array with elements uniformly distributed between 0 and 1
        
    def __pickTheClosestDemo(self):
        '''Picking the values that are closest to 0.5 in each row without using more convenient numpy argmin() 
            and absolute() method instead of abs() and argsort()
        '''
        selection_array_row = np.arange(10)     #configuring the vertical selection array
        selection_array_column = np.argmin(np.absolute(self._the_array-0.5),axis=1) #horizontal selection array
        return self._the_array[selection_array_row,selection_array_column] #use fancy selection to pick values that are closest to 0.5
    
    def __pickTheClosestDemo2(self):
        '''Picking the values that are closest to 0.5 in each row using abs() and argsort()'''
        rearranged_array = abs(self._the_array-0.5).min(axis=1)
        selection_array_row = np.arange(10)
        selection_array_column = abs(self._the_array - 0.5).argsort()[:, 0].flatten()
        return self._the_array[selection_array_row,selection_array_column]
    
    def demoShow(self):
        print '------------------QUESTION 3-----------------------\n'
        print 'The random array generated by np.random.rand() is:'
        self._displayArrayDemo()
        print 'The values in each row that are closest to 0.5 are:(from row 0 to 9)'
        print self.__pickTheClosestDemo()
        print 'The values in each row that are closest to 0.5: (calculated by abs() and argsort()'
        print self.__pickTheClosestDemo2()
        print '\n'
        
class DemoFour(Demo):
    '''
    In class DemoFour:
    >>>
    Methods : 
        __updateArrayDemo() : initialize and calculate the mandelbrot iterations
        __imageShowDemo() : plot and save the image
    '''
    
    def __updateArrayDemo(self):
        try:
            iterator = MandelbrotIterator(params=[-2,1,-1.5,1.5],steps=[300,300],N_max=50)
        except (ParameterFormatError,ParameterTypeError):
            print >> sys.stderr, 'Unexpected Parameters\n'
            sys.exit()
        self._the_array = iterator.getValue()
        
    def __imageShowDemo(self,threshold=50):
        '''Create a boolean mask for items in mandelbrot set and show/save image to \'mandelbrot.png\''''
        mask = np.absolute(self._the_array) < threshold
        #plt.ioff()
        fig = plt.figure()
        ax = fig.add_subplot(1,1,1)
        plt.imshow(mask.T,extent=[-2,1,-1.5,1.5])
        plt.gray()
        plt.show()  #showing off the image
        plt.savefig('mandelbrot.png')
        
    def demoShow(self):
        print '------------------QUESTION 4-----------------------\n'
        print 'Please see the image for more information\n'
        self.__updateArrayDemo()
        self.__imageShowDemo()
        