#This file is part of Tryton.  The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
from decimal import Decimal
import copy
from trytond.model import ModelView, ModelSQL, fields
from trytond.wizard import Wizard, StateView, StateAction, Button
from trytond.pyson import Eval, PYSONEncoder
from trytond.transaction import Transaction
from trytond.pool import Pool


class Account(ModelSQL, ModelView):
    'Analytic Account'
    _name = 'analytic_account.account'
    _description = __doc__

    name = fields.Char('Name', required=True, translate=True, select=True)
    code = fields.Char('Code', select=True)
    active = fields.Boolean('Active', select=True)
    company = fields.Many2One('company.company', 'Company')
    currency = fields.Many2One('currency.currency', 'Currency', required=True)
    currency_digits = fields.Function(fields.Integer('Currency Digits',
        on_change_with=['currency']), 'get_currency_digits')
    type = fields.Selection([
        ('root', 'Root'),
        ('view', 'View'),
        ('normal', 'Normal'),
        ], 'Type', required=True)
    root = fields.Many2One('analytic_account.account', 'Root', select=True,
        domain=[('parent', '=', None)],
        states={
            'invisible': Eval('type') == 'root',
            'required': Eval('type') != 'root',
            },
        depends=['type'])
    parent = fields.Many2One('analytic_account.account', 'Parent', select=True,
        domain=[('parent', 'child_of', Eval('root'))],
        states={
            'invisible': Eval('type') == 'root',
            'required': Eval('type') != 'root',
            },
        depends=['root', 'type'])
    childs = fields.One2Many('analytic_account.account', 'parent', 'Children')
    balance = fields.Function(fields.Numeric('Balance',
        digits=(16, Eval('currency_digits', 1)), depends=['currency_digits']),
        'get_balance')
    credit = fields.Function(fields.Numeric('Credit',
        digits=(16, Eval('currency_digits', 2)), depends=['currency_digits']),
        'get_credit_debit')
    debit = fields.Function(fields.Numeric('Debit',
        digits=(16, Eval('currency_digits', 2)), depends=['currency_digits']),
        'get_credit_debit')
    state = fields.Selection([
        ('draft', 'Draft'),
        ('opened', 'Opened'),
        ('closed', 'Closed'),
        ], 'State', required=True)
    note = fields.Text('Note')
    display_balance = fields.Selection([
        ('debit-credit', 'Debit - Credit'),
        ('credit-debit', 'Credit - Debit'),
        ], 'Display Balance', required=True)
    mandatory = fields.Boolean('Mandatory', states={
            'invisible': Eval('type') != 'root',
            },
        depends=['type'])

    def __init__(self):
        super(Account, self).__init__()
        self._constraints += [
            ('check_recursion', 'recursive_accounts'),
        ]
        self._error_messages.update({
            'recursive_accounts': 'You can not create recursive accounts!',
        })
        self._order.insert(0, ('code', 'ASC'))

    def default_active(self):
        return True

    def default_company(self):
        return Transaction().context.get('company')

    def default_currency(self):
        company_obj = Pool().get('company.company')
        if Transaction().context.get('company'):
            company = company_obj.browse(Transaction().context['company'])
            return company.currency.id

    def default_type(self):
        return 'normal'

    def default_state(self):
        return 'draft'

    def default_display_balance(self):
        return 'credit-debit'

    def default_mandatory(self):
        return False

    def on_change_with_currency_digits(self, vals):
        currency_obj = Pool().get('currency.currency')
        if vals.get('currency'):
            currency = currency_obj.browse(vals['currency'])
            return currency.digits
        return 2

    def get_currency_digits(self, ids, name):
        res = {}
        for account in self.browse(ids):
            res[account.id] = account.currency.digits
        return res

    def get_balance(self, ids, name):
        res = {}
        line_obj = Pool().get('analytic_account.line')
        currency_obj = Pool().get('currency.currency')
        cursor = Transaction().cursor

        child_ids = self.search([('parent', 'child_of', ids)])
        all_ids = {}.fromkeys(ids + child_ids).keys()

        id2account = {}
        accounts = self.browse(all_ids)
        for account in accounts:
            id2account[account.id] = account

        line_query = line_obj.query_get()
        cursor.execute('SELECT a.id, ' \
                    'SUM((COALESCE(l.debit, 0) - COALESCE(l.credit, 0))), ' \
                    'c.currency ' \
                'FROM analytic_account_account a ' \
                    'LEFT JOIN analytic_account_line l ' \
                    'ON (a.id = l.account) ' \
                    'LEFT JOIN account_move_line ml ' \
                    'ON (ml.id = l.move_line) ' \
                    'LEFT JOIN account_account aa ' \
                    'ON (aa.id = ml.account) ' \
                    'LEFT JOIN company_company c ' \
                    'ON (c.id = aa.company) ' \
                'WHERE a.type != \'view\' ' \
                    'AND a.id IN (' + \
                        ','.join(('%s',) * len(all_ids)) + ') ' \
                    'AND ' + line_query + ' ' \
                    'AND a.active ' \
                'GROUP BY a.id, c.currency', all_ids)
        account_sum = {}
        id2currency = {}
        for account_id, sum, currency_id in cursor.fetchall():
            account_sum.setdefault(account_id, Decimal('0.0'))
            if currency_id != id2account[account_id].currency.id:
                currency = None
                if currency_id in id2currency:
                    currency = id2currency[currency_id]
                else:
                    currency = currency_obj.browse(currency_id)
                    id2currency[currency.id] = currency
                account_sum[account_id] += currency_obj.compute(currency, sum,
                        id2account[account_id].currency, round=True)
            else:
                account_sum[account_id] += currency_obj.round(
                        id2account[account_id].currency, sum)

        for account_id in ids:
            res.setdefault(account_id, Decimal('0.0'))
            child_ids = self.search([
                ('parent', 'child_of', [account_id]),
                ])
            to_currency = id2account[account_id].currency
            for child_id in child_ids:
                from_currency = id2account[child_id].currency
                res[account_id] += currency_obj.compute(from_currency,
                        account_sum.get(child_id, Decimal('0.0')), to_currency,
                        round=True)
            res[account_id] = currency_obj.round(to_currency, res[account_id])
            if id2account[account_id].display_balance == 'credit-debit':
                res[account_id] = - res[account_id]
        return res

    def get_credit_debit(self, ids, name):
        res = {}
        line_obj = Pool().get('analytic_account.line')
        currency_obj = Pool().get('currency.currency')
        cursor = Transaction().cursor

        if name not in ('credit', 'debit'):
            raise Exception('Bad argument')

        id2account = {}
        accounts = self.browse(ids)
        for account in accounts:
            res[account.id] = Decimal('0.0')
            id2account[account.id] = account

        line_query = line_obj.query_get()
        cursor.execute('SELECT a.id, ' \
                    'SUM(COALESCE(l.' + name + ', 0)), ' \
                    'c.currency ' \
                'FROM analytic_account_account a ' \
                    'LEFT JOIN analytic_account_line l ' \
                    'ON (a.id = l.account) ' \
                    'LEFT JOIN account_move_line ml ' \
                    'ON (ml.id = l.move_line) ' \
                    'LEFT JOIN account_account aa ' \
                    'ON (aa.id = ml.account) ' \
                    'LEFT JOIN company_company c ' \
                    'ON (c.id = aa.company) ' \
                'WHERE a.type != \'view\' ' \
                    'AND a.id IN (' + \
                        ','.join(('%s',) * len(ids)) + ') ' \
                    'AND ' + line_query + ' ' \
                    'AND a.active ' \
                'GROUP BY a.id, c.currency', ids)

        id2currency = {}
        for account_id, sum, currency_id in cursor.fetchall():
            if currency_id != id2account[account_id].currency.id:
                currency = None
                if currency_id in id2currency:
                    currency = id2currency[currency_id]
                else:
                    currency = currency_obj.browse(currency_id)
                    id2currency[currency.id] = currency
                res[account_id] += currency_obj.compute(currency, sum,
                        id2account[account_id].currency, round=True)
            else:
                res[account_id] += currency_obj.round(
                        id2account[account_id].currency, sum)
        return res

    def get_rec_name(self, ids, name):
        if not ids:
            return {}
        res = {}
        for account in self.browse(ids):
            if account.code:
                res[account.id] = account.code + ' - ' + unicode(account.name)
            else:
                res[account.id] = unicode(account.name)
        return res

    def search_rec_name(self, name, clause):
        ids = self.search([('code',) + clause[1:]], limit=1)
        if ids:
            return [('code',) + clause[1:]]
        else:
            return [(self._rec_name,) + clause[1:]]

    def convert_view(self, tree):
        res = tree.xpath('//field[@name=\'analytic_accounts\']')
        if not res:
            return
        element_accounts = res[0]

        root_account_ids = self.search([
            ('parent', '=', None),
            ])
        if not root_account_ids:
            element_accounts.getparent().getparent().remove(
                    element_accounts.getparent())
            return
        for account_id in root_account_ids:
            newelement = copy.copy(element_accounts)
            newelement.tag = 'label'
            newelement.set('name', 'analytic_account_' + str(account_id))
            element_accounts.addprevious(newelement)
            newelement = copy.copy(element_accounts)
            newelement.set('name', 'analytic_account_' + str(account_id))
            element_accounts.addprevious(newelement)
        parent = element_accounts.getparent()
        parent.remove(element_accounts)

    def analytic_accounts_fields_get(self, field, fields_names=None):
        res = {}
        if fields_names is None:
            fields_names = []

        root_account_ids = self.search([
            ('parent', '=', None),
            ])
        for account in self.browse(root_account_ids):
            name = 'analytic_account_' + str(account.id)
            if name in fields_names or not fields_names:
                res[name] = field.copy()
                res[name]['required'] = account.mandatory
                res[name]['string'] = account.name
                res[name]['relation'] = self._name
                res[name]['domain'] = PYSONEncoder().encode([
                    ('root', '=', account.id),
                    ('type', '=', 'normal')])
        return res

Account()


class OpenChartAccountStart(ModelView):
    'Open Chart of Accounts'
    _name = 'analytic_account.open_chart.start'
    _description = __doc__
    start_date = fields.Date('Start Date')
    end_date = fields.Date('End Date')

OpenChartAccountStart()


class OpenChartAccount(Wizard):
    'Open Chart of Accounts'
    _name = 'analytic_account.open_chart'

    start = StateView('analytic_account.open_chart.start',
        'analytic_account.open_chart_start_view_form', [
            Button('Cancel', 'end', 'tryton-cancel'),
            Button('Open', 'open_', 'tryton-ok', default=True),
            ])
    open_ = StateAction('analytic_account.act_account_tree2')

    def do_open_(self, session, action):
        action['pyson_context'] = PYSONEncoder().encode({
                'start_date': session.start.start_date,
                'end_date': session.start.end_date,
                })
        return action, {}

    def transition_open_(self, session):
        return 'end'

OpenChartAccount()


class AccountSelection(ModelSQL, ModelView):
    'Analytic Account Selection'
    _name = 'analytic_account.account.selection'
    _description = __doc__
    _rec_name = 'id'

    accounts = fields.Many2Many(
            'analytic_account.account-analytic_account.account.selection',
            'selection', 'account', 'Accounts')

    def __init__(self):
        super(AccountSelection, self).__init__()
        self._constraints += [
            ('check_root', 'root_account'),
        ]
        self._error_messages.update({
            'root_account': 'Can not have many accounts with the same root ' \
                    'or a missing mandatory root account!',
        })

    def check_root(self, ids):
        "Check Root"
        account_obj = Pool().get('analytic_account.account')

        root_account_ids = account_obj.search([
            ('parent', '=', None),
            ])
        root_accounts = account_obj.browse(root_account_ids)

        selections = self.browse(ids)
        for selection in selections:
            roots = []
            for account in selection.accounts:
                if account.root.id in roots:
                    return False
                roots.append(account.root.id)
            if Transaction().user:  # Root can by pass
                for account in root_accounts:
                    if account.mandatory:
                        if not account.id in roots:
                            return False
        return True

AccountSelection()


class AccountAccountSelection(ModelSQL):
    'Analytic Account - Analytic Account Selection'
    _name = 'analytic_account.account-analytic_account.account.selection'
    _description = __doc__
    _table = 'analytic_account_account_selection_rel'
    selection = fields.Many2One('analytic_account.account.selection',
            'Selection', ondelete='CASCADE', required=True, select=True)
    account = fields.Many2One('analytic_account.account', 'Account',
            ondelete='RESTRICT', required=True, select=True)

AccountAccountSelection()
