#
#    Copyright (C) 2002-2014  Corporation of Balclutha. All rights Reserved.
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
#    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
#    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
#    ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
#    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
#    CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
#    GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
#    HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
#    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
#    OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#

# import stuff
import AccessControl, types, operator
from AccessControl.Permissions import view_management_screens, manage_properties,\
     access_contents_information
from StringIO import StringIO
from DateTime import DateTime
import Acquisition, string
from Acquisition import aq_base
from OFS.PropertyManager import PropertyManager
from OFS.ObjectManager import REPLACEABLE
from Products.PageTemplates.PageTemplateFile import PageTemplateFile

from Products.BastionBanking.ZCurrency import ZCurrency

from utils import _mime_str, assert_currency
from BLBase import *
from BLSubsidiaryLedger import BLSubsidiaryLedger
from BLTransactionTemplate import BLTransactionTemplate
from BLSubsidiaryAccount import BLSubsidiaryAccount
from BLAccount import BLAccount, addBLAccount
from BLProcess import BLProcess
from Permissions import OperateBastionLedgers, ManageBastionLedgers
from Exceptions import LedgerError, ProcessError
from Products.CMFCore import permissions
from interfaces.process import IProcess

from zope.interface import Interface, implements

class IShareholderLedger(Interface): pass

manage_addBLShareDefinitionForm = PageTemplateFile('zpt/add_sharedefinition', globals())
def manage_addBLShareDefinition(self, id, title='', face=None, allocated=0,
                                issue_date=None, voting_class='A', REQUEST=None):
    """
    """
    if not face:
        face = ZCurrency('%s 1.00' % self.defaultCurrency())
    elif not isinstance(face, ZCurrency):
        face = ZCurrency(face)
    self._setObject(id, BLShareDefinition(id, 
                                          title, 
                                          face,  
                                          allocated, 
                                          issue_date or DateTime(), 
                                          voting_class))
    if REQUEST is not None:
        return self.manage_main(self, REQUEST)
    return id

class BLShareDefinition( PropertyManager, PortalContent ):
    """
    The definition of a company share
    """
    meta_type = portal_type = 'BLShareDefinition'
    _properties = ( 
        {'id':'id',             'type':'string',   'mode':'r'},
        {'id':'title',          'type':'string',   'mode':'w'},
        {'id':'face',           'type':'currency', 'mode':'r'},
        {'id':'allocated',      'type':'long',     'mode':'w'},
        {'id':'issue_date',     'type':'date',     'mode':'w'},
        {'id':'voting_class',   'type':'string',   'mode':'w'},
        )

    property_extensible_schema__ = 0
    __ac_permissions__ = (
        (view, ('allotted', 'shareholders')), #TODO - fix this (ie FSEntryTemplate permissions!!)
        (OperateBastionLedgers, ('manage_propertiesForm', 'manage_editProperties','editProperties')),
        )

    voting_class = ''

    manage_options = (
        { 'label':'Properties', 'action':'manage_propertiesForm',
          'help':('BastionLedger', 'share_defs.stx') },
        ) + PortalContent.manage_options

    def __init__(self, id, title, face, allocated, issue_date, voting_class):
        assert_currency(face)
        self.id = id
        self.title = title
        self.face = face
        self.allocated = allocated
        self.issue_date = issue_date
        self.voting_class = voting_class

    def Title(self):
        return self.title

    def shareholders(self, effective=None):
        """
        return a list of shareholders who have allotments of this share
        """
        if effective and not isinstance(effective, DateTime):
            effective = DateTime(effective)
        return self.aq_parent.getShareholdersWithShare(self.getId(), effective or DateTime())
    
    def allotted(self, effective=None):
        """ go sum up quantities of stock in shareholder accounts allocations """
        result = 0
        if effective and not isinstance(effective, DateTime):
            effective = DateTime(effective)
        for shareholder in self.shareholders():
            result += shareholder.allocationQuantity(self.getId(), effective or DateTime())
        return result

    def editProperties(self, title, description, face, allocated, issue_date, voting_class):
        """
        Plone edit
        """
        self._updateProperty('title', title)
        self.description = description
        self._updateProperty('face', face)
        self._updateProperty('allocated', allocated)
        if not isinstance(issue_date,DateTime):
            issue_date = DateTime(issue_date)
        self._updateProperty('issue_date', issue_date)
        self._updateProperty('voting_class', voting_class)
        self.reindexObject()

    def getCalls(self):
        """ returns any call objects associated with this definition """
        if self.fullyPaid():
            return []
        if not getattr(aq_base(self), 'calls', None):
            self.calls = Folder('calls')
        return self.calls.objectValues('BLCall')

    def fullyPaid(self, effective=None):
        """
        return whether or not all allocations are fully paid
        """
        if effective and not isinstance(effective, DateTime):
            effective = DateTime(effective)
        for allocation in self.allotments():
            if not allocation.fullyPaid():
                return False
        return True

    def paidUp(self, effective=None):
        """
        return the pro-rated paid-up number of shares
        """
        if effective and not isinstance(effective, DateTime):
            effective = DateTime(effective)
        total = ZCurrency(self.face.currency(), 0)
        for allotment in self.allotments():
            total += allotment.paid()
        return total

    def outstanding(self, effective=None):
        """
        return total callable
        """
        if effective and not isinstance(effective, DateTime):
            effective = DateTime(effective)
        total = ZCurrency(self.face.currency(), 0)
        for allotment in self.allotments():
            total += allotment.outstanding()
        return total


    def maxCallablePercentage(self, effective=None):
        """
        """
        if effective and not isinstance(effective, DateTime):
            effective = DateTime(effective)
        result = 0

        for allotment in self.allotments():
            amax = allotment.maxCallablePercentage()
            if amax and amax > result:
                result = amax

        return result

    def allotments(self, effective=None):
        """
        return all the allotments for this definition
        """
        if effective and not isinstance(effective, DateTime):
            effective = DateTime(effective)
        effective = effective or DateTime()
        results = []
        for shareholder in self.shareholders(effective):
            results.extend(shareholder.allocationValues(self.getId(), effective))
        return results

    def __cmp__(self, other):
        if not isinstance(other, BLShareDefinition):
            return -1

        my_id = self.getId()
        other_id = other.getId()

        if my_id > other_id:
            return -1
        elif my_id < other_id:
            return 1
        return 0

AccessControl.class_init.InitializeClass(BLShareDefinition)


manage_addBLAllocationForm = PageTemplateFile('zpt/add_allocation', globals())
def manage_addBLAllocation(self, id, definition=None, quantity=0, issue_date=DateTime(),
                           percentage=100, REQUEST=None):
    """
    """
    realself = self.this()
    if definition is None:
        definition = realself.blLedger().shareDefinitionValues()[0].getId()
    self._setObject(id, BLAllocation(id, definition, quantity, issue_date, percentage))
    if REQUEST is not None:
        REQUEST.RESPONSE.redirect('%s/manage_main' % REQUEST['URL3'])
    return id


class BLAllocation(PropertyManager, PortalContent):
    """
    """
    meta_type = portal_type = 'BLAllocation'

    _properties = ( 
        {'id':'id',                'type':'string',    'mode':'r'},
        {'id':'definition',        'type':'selection', 'mode':'w', 'select_variable':'definitions'},
        {'id':'quantity',          'type':'long',      'mode':'w'},
        {'id':'percentage_paid',   'type':'float',     'mode':'w'},
        {'id':'issue_date',        'type':'date',      'mode':'w'},
        )

    __ac_permissions = (
        (OperateBastionLedgers, ('manage_propertiesForm', 'manage_editProperties','editProperties')),
        ) + PortalContent.__ac_permissions__

    manage_options = PropertyManager.manage_options + PortalContent.manage_options

    percentage_paid = 100.0

    def __init__(self, id, definition, qty, issue_date, percentage_paid):
        self.id = id
        self.definition = definition
        self.quantity = qty
        self.issue_date = issue_date
        self.percentage_paid = percentage_paid

    def Title(self):
        """
        if set up, return the issue dated definition title, otherwise just the issue date
        """
        try:
            return "%s - %s" % (self.toLocalizedTime(self.issue_date), self.getDefinition().Title())
        except:
            # hmmm - some old imports are failing ...
            try:
                return self.toLocalizedTime(self.issue_date)
            except:
                return ''

    def editProperties(self, quantity, definition, issue_date, percentage_paid):
        """
        update the quantity, definition
        """
        self._updateProperty('quantity', quantity)
        self._updateProperty('definition', definition)
        self._updateProperty('issue_date', issue_date)
        self._updateProperty('percentage_paid', percentage_paid)

    def getDefinition(self):
        """
        returns the share definition associated with this allocation
        """
        return self.aq_parent.aq_parent._getOb(self.definition)

    def definitions(self):
        return map(lambda x: x.getId(), self.shareDefinitionValues())

    def getCalls(self):
        """
        return a list of call metadata
        """
        return self.getDefinition().getCalls()

    def nominal(self):
        """
        return the nominal value of the allocation
        """
        # hmmm - force it not to do int (repeat) processing - strangely this appears
        # to be returning a sequence ....
        face = self.getDefinition().face
        assert_currency(face)
        return face * float(self.quantity)

    def outstanding(self):
        """
        return any outstanding amount on the allocation
        """
        return self.nominal() * (100.0 - self.percentage_paid) / 100.0

    def paid(self):
        """
        return the paid-up value of the parcel
        """
        return self.nominal() * self.percentage_paid / 100.0
        
    def fullyPaid(self):
        return self.percentage_paid >= 100

    def votingClass(self):
        """ return it's voting class - if blank, this is a non-voting share """
        return self.getDefinition().voting_class 

    def maxCallablePercentage(self):
        return 100.0 - self.percentage_paid

    def _repair(self):
        if not getattr(aq_base(self), 'issue_date', None):
            self.issue_date = DateTime(self.CreationDate())
        if not getattr(aq_base(self), 'definition', None):
            self.definition = self.getId()

AccessControl.class_init.InitializeClass( BLAllocation )

def manage_addBLCall(self, allocation_id, percentage, call_date, required_date, REQUEST=None):
    """
    """
    self._setObject(id, BLCall(id, allocation_id, percentage, call_date, required_date))

    return id
                    

class BLCall(PropertyManager, PortalContent):
    """
    """
    meta_type = portal_type = 'BLCall'

    _properties = ( 
        {'id':'id',            'type':'string',    'mode':'r'},
        {'id':'allocation_id', 'type':'selection', 'mode':'w', 'select_variable':'allocation_ids'},
        {'id':'percentage',    'type':'float',     'mode':'w'},
        {'id':'call_date',     'type':'date',      'mode':'w'},
        {'id':'required_date', 'type':'date',      'mode':'w'},
        )

    def __init__(self, id, allocation_id, percentage, call_date, required_date):
        self.id = id
        self.allocation_id = allocation_id
        self.call_date = call_date
        self.required_date = required_date

    def manage_post(self, REQUEST=None):
        """
        post the call to affected shareholders
        """

    

AccessControl.class_init.InitializeClass(BLCall)


manage_addBLShareholderForm = PageTemplateFile('zpt/add_shareholder', globals())
def manage_addBLShareholder(self, id='', title='', description='', currency='', REQUEST=None):
    """
    """
    #assert accounts.meta_type == 'BLAccounts', 'Incorrect container: %s <> BLShareholderLedger' % self.meta_type
    if not currency:
        currency = self.defaultCurrency()
    if not id:
        id = self.nextAccountId()

    self._setObject(id, BLShareholder(id, title, description, self.accountType(), currency))

    if REQUEST is not None:
        return self.manage_main(self, REQUEST)
    return id

def addBLShareholder(self, id, title=''):
    """ Plone ctor """
    id = manage_addBLShareholder(self, id=id, title=title)
    return id

class BLShareholder(BLSubsidiaryAccount):
    """ """
    meta_type = portal_type = 'BLShareholder'

    __ac_permissions = BLSubsidiaryAccount.__ac_permissions__ +(
        (access_contents_information, ('allocationValues', 'allocationQuantity', 'shareDefinitionValues')),
        (view_management_screens, ( 'manage_allocations', 'manage_notices') ),
        (OperateBastionLedgers, ('manage_details',)),
        )

    # we want to be able to shove allocations named as their share def
    __replaceable__ = REPLACEABLE
    
    _properties =  BLSubsidiaryAccount._properties + (
        {'id':'address',        'type':'text',      'mode': 'w'},
        {'id':'account_number', 'type':'string',    'mode': 'w'},
    )

    def manage_options(self):
        options = [ BLSubsidiaryAccount.manage_options(self)[0],
                    {'label':'Advices', 'action':'manage_notices'},
                    {'label':'Allocations', 'action':'manage_btree'}]
        options.extend(BLSubsidiaryAccount.manage_options(self)[1:])
        return options

    manage_notices = PageTemplateFile('zpt/shareholder_notices', globals())
    #index_html = PageTemplateFile('zpt/shareholderaccount_index', globals())

    def __init__(self, id, title, description, type, currency):
        BLSubsidiaryAccount.__init__(self, id, title, description, type, 'ShareHolder', currency, id)
        self.address=''
        self.email=''
        self.account_number=''

    def all_meta_types(self):
        return [ ProductsDictionary('BLAllocation') ]

    def allocationValues(self, definition='', effective=None):
        """
        returns a list of allocations of a share at given date, or all allocations
        for a given date
        """
        if effective and not isinstance(effective, DateTime):
            effective = DateTime(effective)
        effective = effective or DateTime()
        if definition:
            return filter(lambda x: x.issue_date <= effective and x.definition==definition,
                          self.objectValues('BLAllocation'))
        else:
            return filter(lambda x: x.issue_date <= effective, self.objectValues('BLAllocation'))

    def allocationQuantity(self, definition, effective=None):
        """
        returns physical quantity of a share held at given date
        """
        if effective and not isinstance(effective, DateTime):
            effective = DateTime(effective)
        effective = effective or DateTime()
        allocations = self.allocationValues(definition, effective)
        if allocations:
            return reduce(operator.add, map(lambda x: x.quantity, allocations))
        return 0

    def shareDefinitionValues(self, effective=None):
        """
        return a list of the stock types held by the shareholder at a given date
        """
        defs = []
        for allocation in self.allocationValues(effective=effective):
            defn = allocation.definition
            if defn not in defs:
                defs.append(defn)

        if defs:
            return filter(lambda x: x.getId() in defs, self.aq_parent.shareDefinitionValues())
        
        return []

    def manage_emailShareCertificate(self, email, share, message=None,effective=None, REQUEST=None):
        """
        """
        try:
            mailhost = self.superValues(['Mail Host', 'Secure Mail Host'])[0]
        except:
            # TODO - exception handling ...
            if REQUEST:
                REQUEST.set('manage_tabs_message', 'No Mail Host Found')
                return self.manage_main(self, REQUEST)
            raise ValueError, 'no MailHost found'
        
        sender = self.aq_parent.email
        if not sender:
            if REQUEST:
                REQUEST.set('manage_tabs_message', """Ledger's Correpondence Email unset!""")
                return self.manage_main(self, REQUEST)
            raise LedgerError, """Ledger's Correspo/ndence Email unset!"""

        effective=effective or DateTime()

        # ensure 7-bit
        mail_text = str(self.blsharecert_template(self, REQUEST, 
                                                  sender=sender, 
                                                  share=self.aq_parent.shareDefinition(share),
                                                  quantity=self.allocationQuantity(share,effective),
                                                  email=email, 
                                                  effective=effective))

        mailhost._send(sender, email, mail_text)

        if REQUEST:
            REQUEST.set('manage_tabs_message', 'Statement emailed to %s' % email)
            return self.manage_main(self, REQUEST)

    def _repair(self):
        """
        """
        if getattr(aq_base(self), 'Allocations', None):
            for allocation in self.Allocations.objectValues():
                if not getattr(aq_base(self), allocation.getId(), None):
                    self._setObject(allocation.getId(), allocation)
        BLSubsidiaryAccount._repair(self)
            
AccessControl.class_init.InitializeClass( BLShareholder )
    
manage_addBLShareholderLedgerForm = PageTemplateFile('zpt/add_shareholderledger', globals())
def manage_addBLShareholderLedger(self, id, controlAccounts, title='', REQUEST=None):
    """ adds a user container """
    try:
        # do some checking ...
        controls = []
        currency = ''
        for acc in controlAccounts:
            if type(acc) == types.StringType:
                control = self.Ledger._getOb(acc)
            else:
                control = acc
            assert control.meta_type =='BLAccount', "Incorrect Control Account Type - Must be strictly GL"
            controls.append(control)
            currency = control.base_currency
        #assert not getattr(aq_base(control), id, None),"A Subsidiary Ledger Already Exists for this account."
        self._setObject(id, BLShareholderLedger(id, title, controls, [currency]))
    except:
        # TODO: a messagedialogue ..
        raise
    
    if REQUEST is not None:
        return self.manage_main(self, REQUEST)
    return id

def addBLShareholderLedger(self, id, title='', REQUEST=None):
    """
    Plone Add
    """
    control = addBLAccount(self.Ledger, id, title)
    manage_addBLShareholderLedger(self, id, (control,), title=title)
    return id



class BLShareholderLedger(BLSubsidiaryLedger):
    """ """
    meta_type = portal_type = 'BLShareholderLedger'

    account_types = ('BLShareholder',)
    __ac_permissions__ = BLSubsidiaryLedger.__ac_permissions__ + (
        (access_contents_information, ('shareDefinitionValues', 'shareDefinition', 
                                       'votingClasses', 'voters', 'votesForVoter', 
                                       'votesForAccount',
                                       'voterInfoFor', 'voterInfo', 'isJointHolding', 
                                       'otherHolders',)),
        (OperateBastionLedgers, ('getShareholdersWithShare',) ),
        )

    implements(IShareholderLedger)

    #manage_mail = PageTemplateFile('zpt/view_shareholdermail', globals())
    
    def all_meta_types(self):
        t = BLSubsidiaryLedger.all_meta_types(self)
        t.append(ProductsDictionary('BLShareDefinition'))
        return t
    
    def __init__(self, id, title, controls, currencies, account_prefix='S', txn_prefix='S' ):
        BLSubsidiaryLedger.__init__(self, id, title, controls, currencies,
                                    1000000, account_prefix, 1, txn_prefix )

    def getShareholdersWithShare(self, share_type, effective=None):
        """
        helper to return a list of shareholders holding a certain stock type
        """
        if effective and not isinstance(effective, DateTime):
            effective = DateTime(effective)
        shareholders = []
        for shareholder in self.accountValues():
            if shareholder.allocationValues(share_type, effective or DateTime()):
                shareholders.append(shareholder)
        return shareholders

    def shareDefinitionValues(self):
        """
        """
        return self.objectValues('BLShareDefinition')

    def shareDefinition(self, id):
        """
        return the BLShareDefinition specified
        """
        try:
            defn = self._getOb(id)
        except:
            raise KeyError, id
        if not isinstance(defn, BLShareDefinition):
            raise KeyError, id
        return defn

    def _repair(self):
        if getattr(aq_base(self), 'Shares', None):
            for defn in self.Shares.objectValues():
                if not getattr(aq_base(self), defn.getId(), None):
                    self._setObject(defn.getId(), defn)
            try:
                delattr(self, 'Shares')
            except:
                pass
        BLSubsidiaryLedger._repair(self)

    def votingClasses(self):
        """
        return a list of voting class names for the ledger
        """
        results = {}
        for k in filter(lambda x:x,
                        map(lambda x: x.voting_class, 
                            self.shareDefinitionValues())):
            results[k] = 1
        return results.keys()

    def voters(self, effective=None):
        """
        return a list of userids of shareholder's (and proxy's) for all accounts
        with allocations with voting rights
        """
        voters = {}
        if effective and not isinstance(effective, DateTime):
            effective = DateTime(effective)
        effective = effective or DateTime()
        for shareholder in self.accountValues(status='open'):
            if filter(lambda x: x.votingClass(),
                      shareholder.allocationValues(effective=effective)):
                for k in shareholder.getMemberIds():
                    voters[k] = 1
        return voters.keys()
    
    def votesForAccount(self, voting_class, accno, effective=None):
        """
        return the number of votes an account has
        """
        total = 0
        if effective and not isinstance(effective, DateTime):
            effective = DateTime(effective)
        effective = effective or DateTime()

        if type(voting_class) == types.StringType:
            voting_class = [ voting_class ]

        try:
            account = self._getOb(accno)
        except:
            raise KeyError, accno

        for defn in filter(lambda x: x.voting_class in voting_class, 
                           self.shareDefinitionValues()):
            total += account.allocationQuantity(defn.getId(), effective)

        return total

    def votesForVoter(self, voting_class, userid, effective=None):
        """
        return the number of shares this user is eligible to vote for
        """
        total = 0
        if effective and not isinstance(effective, DateTime):
            effective = DateTime(effective)
        effective = effective or DateTime()
        if type(voting_class) == types.StringType:
            voting_class = [ voting_class ]
        share_defs = filter(lambda x: x.voting_class in voting_class, 
                            self.shareDefinitionValues())

        mt = getToolByName(self, 'portal_membership')

        for share in share_defs:
            for shareholder in self.getShareholdersWithShare(share.getId(), effective):
                owners = shareholder.getMemberIds(mt)
                if userid in owners:
                    total += shareholder.allocationQuantity(share.getId(), effective)

        return total

    def voterInfoFor(self, userid, effective=None):
        """
        return a list of tuples, class, number of votes, for the specified user
        """
        if effective and not isinstance(effective, DateTime):
            effective = DateTime(effective)
        effective = effective or DateTime()
        # TODO - optimise all this ...
        results = []
        for vclass in self.votingClasses():
            total = self.votesForVoter(vclass, userid, effective)
            if total:
                results.append({'class':vclass, 
                                'votes':total, 
                                'joint':self.isJointHolding(vclass, userid, effective)})
        return results

    def voterInfo(self, effective=None):
        """
        return a list of voter info dictionaries
        """
        if effective is None:
            effective = DateTime()
        elif not isinstance(effective, DateTime):
            effective = DateTime(effective)
        results = []
        for userid in self.voters(effective):
            for info in self.voterInfoFor(userid):
                if info['votes']:
                    info['userid'] = userid
                    results.append(info)
        return results

    def isJointHolding(self, voting_class, userid, effective=None):
        """
        returns whether or not the class is a joint holding for voting
        """
        if effective and not isinstance(effective, DateTime):
            effective = DateTime(effective)
        mt = getToolByName(self, 'portal_membership')
        for shareholder in self.getShareholdersWithShare(voting_class, effective or DateTime()):
            owners = shareholder.getMemberIds(mt)
            if userid in owners and len(owners) > 1:
                return True
        return False

    def otherHolders(self, voting_class, userid, effective=None):
        """
        return a list of other members jointly holding shares - an empty list 
        signifies a severally held share
        """
        if effective and not isinstance(effective, DateTime):
            effective = DateTime(effective)
        results = []
        mt = getToolByName(self, 'portal_membership')
        for shareholder in self.getShareholdersWithShare(voting_class, effective or DateTime()):
            owners = shareholder.getMemberIds(mt)
            if userid in owners and len(owners) > 1:
                for owner in filter(lambda x: x != userid):
                    if owner not in results:
                        results.append(owner)
        return results

    def votesByAccount(self, voting_classes, effective=None):
        """
        return a list of accno, (class, max_votes) tuples
        """
        results = {}
        for defn in self.shareDefinitionValues():
            if defn.voting_class in voting_classes:
                for shareholder in self.accountValues():
                    votes = shareholder.allocationQuantity(defn.getId(), effective)
                    if votes:
                        key = shareholder.getId()
                        if results.has_key(key):
                            results[key][defn.voting_class] = votes
                        else:
                            results[key] = { defn.voting_class : votes }
        votes = []
        for k,v in results.items():
            votes.append((k, v))

        return votes
            
AccessControl.class_init.InitializeClass( BLShareholderLedger )
    


def manage_addBLDividendAdvice(self, id, title, qty, type, amt, effective, REQUEST=None):
    """
    """
    assert self.this().meta_type == 'BLShareholder', 'doh - not a shareholder!'
    self._setObject(id, BLDividendAdvice(id, title, qty, type, abs(amt), effective))

    if REQUEST:
        REQUEST.RESPONSE.redirect('%s/manage_main' % REQUEST['URL3'])


class BLDividendAdvice(PortalContent):
    """
    A shareholder's dividend advice
    """
    meta_type = portal_type = 'BLDividendAdvice'

    implements(IProcess)

    manage_options = (
        {'label':'Advice', 'action':'manage_main'},
        {'label':'View',   'action':''},
        {'label':'Properties', 'action':'manage_propertiesForm'},
        ) + PortalContent.manage_options

    __ac_permissions__ = PortalContent.__ac_permissions__ + (
        (view, ('manage_download',)),
        )

    _properties = PortalContent._properties + (
        {'id':'quantity',       'type':'int',      'mode':'w'},
        {'id':'share_type',     'type':'string',   'mode':'r'},
        {'id':'amount',         'type':'currency', 'mode':'w'},
        {'id':'effective_date', 'type':'date',     'mode':'w'}, # Dublin Core attr
        )
    
    manage_main = PageTemplateFile('zpt/dividend_advice', globals())
    download_image = PageTemplateFile('zpt/dividend_advice_index', globals())

    def __init__(self, id, title, qty, type, amt, effective):
        self.id = id
        self.title = title
        self.quantity = qty
        self.share_type = type
        self.amount = amt
        self.effective_date = effective


    def manage_download(self, REQUEST, RESPONSE):
        """
        download the advice to local machine
        """
        RESPONSE.setHeader('Content-Type', 'application/octet-stream')
        RESPONSE.setHeader('Content-Disposition', 'filename=%s.html' % self.id)
        RESPONSE.write(self.download_image(self, REQUEST))

    def __cmp__(self, other):
        """
        sort order based upon effective date
        """
        self_dt = self.effective()
        other_dt = other.effective()

        if self_dt > other_dt: return 1
        if self_dt < other_dt: return -1
        return 0
        
AccessControl.class_init.InitializeClass(BLDividendAdvice)


class BLDividendProcess(BLProcess):
    """
    Paying a dividend
    """
    meta_type = portal_type = 'BLDividendProcess'

    parameterMap = (
        {'id':'share_type',   'title':'Share Type',   'type':'string',   'description':''},
        {'id':'total_amount', 'title':'Total Amount', 'type':'currency', 'description':''},
        {'id':'effective',    'title':'Effective',    'type':'date',     'description':''},
        {'id':'do_payment',   'title':'Do Payment?',  'type':'boolean',  'description':''},
        )
    
    def manage_run(self, share_type, total_amount, effective=None, do_payment=False):
        """
        """
        ledger = self.aq_parent
        if effective and not isinstance(effective, DateTime):
            effective = DateTime(effective)
        effective = effective or DateTime()
        sharedefn = getattr(ledger, share_type, None)

        if not sharedefn:
            raise ProcessError, 'Share Definition not found: %s' % share_type

        accounts = ledger.getShareholdersWithShare(share_type, effective)

        if not isinstance(total_amount, ZCurrency):
            total_amount = ZCurrency(total_amount)

        if not isinstance(effective, DateTime):
	    effective = DateTime(effective)

        if accounts:

            payable = ledger.blp_dividend_payable.generate(accounts,
                                                           effective=effective,
                                                           total_amount=total_amount,
                                                           share_type=share_type,
                                                           control=self.Ledger.accountIds(tags='dividend_payable')[0])

            for shareholder in accounts:
                manage_addBLDividendAdvice(shareholder,
                                           '%s-%s' % (share_type, effective.strftime('%Y%m%d')),
                                           '%s Dividend' % share_type,
                                           shareholder.allocationQuantity(share_type, effective),
                                           share_type,
                                           payable.blEntry(shareholder.getId()).amount,
                                           effective)

            if do_payment:
                payment = self.blp_dividend_payment.generate(accounts,
                                                             effective=effective,
                                                             total_amount=total_amount,
                                                             share_type=share_type,
                                                             control=self.Ledger.accountIds(tags='dividend_payable')[0])

    def processFor(self, ledger):
        """
        conditions for which this process is available
        """
        if ledger.meta_type == 'BLShareholderLedger' and \
               ledger.shareDefinitionValues():
            return True
        return False
    
AccessControl.class_init.InitializeClass(BLDividendProcess)

#
# these are to be removed once conversions completed ...
#
class BLShareDefinitions( PortalFolder): pass
class BLAllocations( PortalFolder): pass
