"""
Portfolio is a module that defines the portfolio class

Author: Israel Malkin
"""

import pandas as pd
import numpy as np
import datetime
import portfolioFactory
from ..strategy import strategy as strategy
from ..utils import customExceptions  as customExceptions

class portfolio(object):
    
    ''' Porfolio is a class to represent investment porfolios. 
    
    This class contains returns generated by the portfolio and associated metadata.
    
    A portfolio is composed of a linear combination of strategies 
    
    Public Attributes:
        - portReturns (df): a single-column dataframe representing the porfolio returns
        - strategies (list) : a list with the strategy names composing the portfolio
        - scheme (list) : a list containing the weighting of each strategy

    '''


  
    def __init__(self,strategyPool,weights):
        ''' Method to intialize a porfolio object
            
        Arguments:
            strategyPool (list): a list of unique strategy objects
            universe (universe): a list of numeric strategy weights
            
        User input is verified, ensuring:
            - both inputs are lists
            - both input lists are of equal length
            - elements of the strategyPool (first arg) are strategy objects
            - elements of weights (second arg) are numeric 
            - there are no duplicate strategies
            
        If any of the checks do not pass, an exception is raised
        
        '''
        
        # confirm both inputs are lists
        if type(strategyPool)!=list or type(weights)!=list:
            raise customExceptions.notListError
        
        # confirm lists are the same length
        if len(strategyPool)!=len(weights):
            raise customExceptions.listMismatchError
        
        # confirm elements of strategyPool are strategy objects
        for s in strategyPool:
            if isinstance(s,portfolioFactory.strategy.strategy.strategy)==False:
                raise customExceptions.notStrategyError
    
        # confirm elements of weights are numeric
        for w in weights:
            if isinstance(w,(int, long, float))==False:
                raise customExceptions.badWeightError

        # make sure there are no duplicate strategies
        strats = [x.parameters['name'] for x in strategyPool]
        if len(strats) > len(set(strats)):
            raise customExceptions.duplicatesError
        
        
        # once input has been verified, set attributes
        self._pool = strategyPool
        self.scheme = weights[:]
        self.strategies = strats
        self.portReturns = self.__calcPort()
        self.weights = self.__calcWeights()





    def __calcPort(self):
        ''' Method to calculate the overall return of a portfolio
        
            The overall returns is calculated as the weighted sum of the strategies
            
            Result:
            Sets self.portReturns with a single-column dataframe with the returns of the portfolio
        '''
        
        portWeights = pd.DataFrame(pd.Series(self.scheme, index=self.strategies))
        portElements = {x.parameters['name'] : x.strategyReturns for x in self._pool}
        self._portData = pd.DataFrame(portElements)
        
        convertFunction = lambda x: float(x[0])
        return self._portData.dot(portWeights).apply(convertFunction,axis=1)
        

    def __calcWeights(self):
        ''' Method to calculate the overall weight in a particular investment
        
            The weight of each investment is calculated as the the weighted sum of the strategy-specific weights.
            
            Results:
            Sets self.weights with the overall weight attributed to each possible investment
        '''
        #create a dictionary with strategy name as key and loading as value
        loadingsMap = {}
        for i in range(0,len(self.strategies)):
            loadingsMap[self._pool[i].parameters['name']] = self.scheme[i]
            
        #create a dictionary with strategy name as key and investment weights as value
        strategyMap = {x.parameters['name'] : x.weights for x in self._pool}   
        
        # intialize a empty dataframe
        allTickers = []
        for x in self._pool:
            allTickers.extend(x.weights.columns)
        allTickers = set(allTickers)
        
        weightsTemplate=pd.DataFrame(0,index=self._portData.index,columns=allTickers)        
        
        # then add (invest weights X loadings) piecewise
        for i in strategyMap.keys():
            weightsTemplate = weightsTemplate + (loadingsMap[i]*strategyMap[i])
        
        return weightsTemplate
            