# -*- encoding: utf-8 -*-
##############################################################################
#
#    Author: Nicolas Bessi. Copyright Camptocamp SA
#    Donors: Hasa Sàrl, Open Net Sàrl and Prisme Solutions Informatique SA
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################

import time
from datetime import datetime
import base64

from osv import osv, fields
import pooler
from tools.translate import _
import unicode2ascii

import re

TRANS=[
    (u'é','e'),
    (u'è','e'),
    (u'à','a'),
    (u'ê','e'),
    (u'î','i'),
    (u'ï','i'),
    (u'â','a'),
    (u'ä','a'),
]

def _u2a(text) :
    """Tries to convert unicode charactere to asci equivalence"""
    if not text : return ""
    txt = ""
    for c in text:
        if ord(c) < 128 :
            txt += c
        elif c in unicode2ascii.EXTRA_LATIN_NAMES :
            txt += unicode2ascii.EXTRA_LATIN_NAMES[c]
        elif c in unicode2ascii.UNI2ASCII_CONVERSIONS :
            txt += unicode2ascii.UNI2ASCII_CONVERSIONS[c]
        elif c in unicode2ascii.EXTRA_CHARACTERS :
            txt += unicode2ascii.EXTRA_CHARACTERS[c]
        elif c in unicode2ascii.FG_HACKS :
            txt += unicode2ascii.FG_HACKS[c]
        else : txt+= "_"
    return txt

def tr(string_in):
    try:
        string_in= string_in.decode('utf-8')
    except:
        # If exception => then just take the string as is
        pass
    for k in TRANS:
        string_in = string_in.replace(k[0],k[1])
    try:
        res= string_in.encode('ascii','replace')
    except:
        res = string_in
    return res


class record:
    def __init__(self, global_context_dict):
        for i in global_context_dict:
            global_context_dict[i] = global_context_dict[i] \
                    and tr(global_context_dict[i])
        self.fields = []
        self.global_values = global_context_dict
        self.pre = {
            'padding': '',
            'seg_num1': '01',
            'seg_num2': '02',
            'seg_num3': '03',
            'seg_num4': '04',
            'seg_num5': '05',
            'flag': '0',
            'zero5': '00000'
        }
        self.post={'date_value_hdr': '000000', 'type_paiement': '0'}
        self.init_local_context()

    def init_local_context(self):
        """
        Must instanciate a fields list, field = (name,size)
        and update a local_values dict.
        """
        raise _('not implemented')

    def generate(self):
        res=''
        for field in self.fields :
            if self.pre.has_key(field[0]):
                value = self.pre[field[0]]
            elif self.global_values.has_key(field[0]):
                value = self.global_values[field[0]]
            elif self.post.has_key(field[0]):
                value = self.post[field[0]]
            else :
                pass
            try:
                res = res + c_ljust(value, field[1])
            except :
                pass
        return res


class record_gt826(record):
    """
    bvr
    """
    def init_local_context(self):
        self.fields=[
            ('seg_num1', 2),
            #header
            ('date_value_hdr', 6),
            ('partner_bank_clearing', 12),
            ('zero5', 5),
            ('creation_date', 6),
            ('comp_bank_clearing', 7),
            ('uid', 5),
            ('sequence', 5),
            ('genre_trans', 3),
            ('type_paiement', 1),
            ('flag', 1),
            #seg1
            ('comp_dta', 5),
            ('number', 11),
            ('comp_bank_iban', 24),
            ('date_value', 6),
            ('currency', 3),
            ('amount_to_pay', 12),
            ('padding', 14),
            #seg2
            ('seg_num2', 2),
            ('comp_name', 20),
            ('comp_street', 20),
            ('comp_zip', 10),
            ('comp_city', 10),
            ('comp_country', 20),
            ('padding', 46),
            #seg3
            ('seg_num3', 2),
            ('partner_bvr', 12),#numero d'adherent bvr
            ('partner_name', 20),
            ('partner_street', 20),
            ('partner_zip', 10),
            ('partner_city', 10),
            ('partner_country', 20),
            ('reference', 27),#communication structuree
            ('padding', 2),#cle de controle
            ('padding', 5)
        ]
        self.pre.update({
            'date_value_hdr': self.global_values['date_value'],
            'date_value': '',
            'partner_bank_clearing': '',
            'partner_cpt_benef': '',
            'genre_trans': '826',
            'conv_cours': '',
            'option_id_bank': 'D',
            'partner_bvr': '/C/'+ self.global_values['partner_bvr'],
            'ref2': '',
            'ref3': '',
            'format': '0',
        })

class record_gt827(record):
    """
    interne suisse (bvpost et bvbank)
    """
    def init_local_context(self):
        self.fields = [
            ('seg_num1', 2),
            #header
            ('date_value_hdr', 6),
            ('partner_bank_clearing', 12),
            ('zero5', 5),
            ('creation_date', 6),
            ('comp_bank_clearing', 7),
            ('uid', 5),
            ('sequence', 5),
            ('genre_trans', 3),
            ('type_paiement', 1),
            ('flag', 1),
            #seg1
            ('comp_dta', 5),
            ('number', 11),
            ('comp_bank_iban', 24),
            ('date_value', 6),
            ('currency', 3),
            ('amount_to_pay', 12),
            ('padding', 14),
            #seg2
            ('seg_num2', 2),
            ('comp_name', 20),
            ('comp_street', 20),
            ('comp_zip', 10),
            ('comp_city', 10),
            ('comp_country', 20),
            ('padding', 46),
            #seg3
            ('seg_num3', 2),
            ('partner_bank_number', 30),
            ('partner_name', 24),
            ('partner_street', 24),
            ('partner_zip', 12),
            ('partner_city', 12),
            ('partner_country', 24),
            #seg4
            ('seg_num4', 2),
            ('reference', 112),
            ('padding', 14),
            #seg5
            #('padding',128)
            ]

        self.pre.update({
            'date_value_hdr': self.global_values['date_value'],
            'date_value': '',
            'partner_cpt_benef': '',
            'type_paiement': '0',
            'genre_trans': '827',
            'conv_cours': '',
            'option_id_bank': 'D',
            'ref2': '',
            'ref3': '',
            'format': '0'
        })


class record_gt836(record):
    """
    iban
    """
    def init_local_context(self):
        self.fields = [
            ('seg_num1', 2),
            #header
            ('date_value_hdr', 6),
            ('partner_bank_clearing', 12),
            ('zero5', 5),
            ('creation_date', 6),
            ('comp_bank_clearing', 7),
            ('uid', 5),
            ('sequence', 5),
            ('genre_trans', 3),
            ('type_paiement', 1),
            ('flag', 1),
            #seg1
            ('comp_dta', 5),
            ('number', 11),
            ('comp_bank_iban', 24),
            ('date_value', 6),
            ('currency', 3),
            ('amount_to_pay', 15),
            ('padding', 11),
            #seg2
            ('seg_num2', 2),
            ('conv_cours', 12),
            ('comp_name', 35),
            ('comp_street', 35),
            ('comp_country', 3),
            ('comp_zip', 10),
            ('comp_city', 22),
            ('padding', 9),
            #seg3
            ('seg_num3', 2),
            ('option_id_bank', 1),
            ('partner_bank_ident', 70),
            ('partner_bank_iban', 34),
            ('padding', 21),
            #seg4
            ('seg_num4', 2),
            ('partner_name', 35),
            ('partner_street', 35),
            ('partner_country', 3),
            ('partner_zip', 10),
            ('partner_city', 22),
            ('padding', 21),
            #seg5
            ('seg_num5', 2),
            ('option_motif', 1),
            ('reference', 105),
            ('format', 1),
            ('padding', 19)
        ]
        self.pre.update( {
            'partner_bank_clearing': '',
            'partner_cpt_benef': '',
            'type_paiement': '0',
            'genre_trans': '836',
            'conv_cours': '',
            'reference': self.global_values['reference'],
            'ref2': '',
            'ref3': '',
            'format': '2'
        })
        self.post.update({'option_motif': 'U'})


class record_gt890(record):
    """
    Total
    """
    def init_local_context(self):
        self.fields = [
            ('seg_num1', 2),
            #header
            ('date_value_hdr', 6),
            ('partner_bank_clearing', 12),
            ('zero5', 5),
            ('creation_date', 6),
            ('comp_bank_clearing', 7),
            ('uid', 5),
            ('sequence', 5),
            ('genre_trans', 3),
            ('type_paiement', 1),
            ('flag', 1),
            #total
            ('amount_total', 16),
            ('padding', 59)
        ]
        self.pre.update({'partner_bank_clearing': '', 'partner_cpt_benef': '',
            'company_bank_clearing': '', 'genre_trans': '890'})

def c_ljust(s, size):
    """
    check before calling ljust
    """
    s= s or ''
    if len(s) > size:
        s= s[:size]
    s = s.decode('utf-8').encode('latin1','replace').ljust(size)
    return s

def _is_9_pos_bvr_adherent(adherent_num):
    """
    from a bvr adherent number,
    return true if 
    """
    pattern = r'[0-9]{2}-[0-9]{1,6}-[0-9]'
    return re.search(pattern, adherent_num)

def _create_dta(obj, cr, uid, data, context=None):
    v = {}
    v['uid'] = str(uid)
    v['creation_date'] = time.strftime('%y%m%d')
    dta = ''

    pool = pooler.get_pool(cr.dbname)
    payment_obj = pool.get('payment.order')
    attachment_obj = pool.get('ir.attachment')
    if context is None:
        context = {}
    payment = payment_obj.browse(cr, uid, data['id'], context=context)
    # if payment.state != 'done':
        # raise osv.except_osv(_('Order not confirmed'),
        #         _('Please confirm it'))
    if not payment.mode:
        raise osv.except_osv(_('Error'),
                _('No payment mode'))
    bank = payment.mode.bank_id
    if not bank:
        raise osv.except_osv(_('Error'), _('No bank account for the company.'))

    v['comp_bank_name']= bank.bank and bank.bank.name or False
    v['comp_bank_clearing'] = bank.bank.clearing

    if not v['comp_bank_clearing']:
        raise osv.except_osv(_('Error'),
                _('You must provide a Clearing Number for your bank account.'))

    user = pool.get('res.users').browse(cr,uid,[uid])[0]
    company = user.company_id
    #XXX dirty code use get_addr
    co_addr = company.partner_id.address[0]
    v['comp_country'] = co_addr.country_id and co_addr.country_id.name or ''
    v['comp_street'] = co_addr.street or ''
    v['comp_zip'] = co_addr.zip
    v['comp_city'] = co_addr.city
    v['comp_name'] = co_addr.name
    v['comp_dta'] = bank.dta_code or '' #XXX not mandatory in pratice

    # iban and account number are the same field and depends only on the type of account
    v['comp_bank_iban'] = v['comp_bank_number'] = bank.acc_number or ''
    
    #if bank.iban:
    #    v['comp_bank_iban'] = bank.iban.replace(' ','') or ''
    #else:
    #    v['comp_bank_iban'] = ''
    if not v['comp_bank_iban']:
        raise osv.except_osv(_('Error'),
                _('No IBAN for the company bank account.'))

    res_partner_bank_obj = pool.get('res.partner.bank')

    seq = 1
    amount_tot = 0
    amount_currency_tot = 0

    for pline in payment.line_ids:
        if not pline.bank_id:
            raise osv.except_osv(_('Error'), _('No bank account defined\n' \
                    'on line: %s') % pline.name)
        if not pline.bank_id.bank:
            raise osv.except_osv(_('Error'), _('No bank defined\n' \
                    'for the bank account: %s\n' \
                    'on the partner: %s\n' \
                    'on line: %s') + (pline.bank_id.state, pline.partner_id.name, pline.name))

        v['sequence'] = str(seq).rjust(5).replace(' ', '0')
        v['amount_to_pay']= str(pline.amount_currency).replace('.', ',')
        v['number'] = pline.name
        v['currency'] = pline.currency.name

        v['partner_bank_name'] =  pline.bank_id.bank.name or False
        v['partner_bank_clearing'] =  pline.bank_id.bank.clearing or False
        if not v['partner_bank_name'] :
            raise osv.except_osv(_('Error'), _('No bank name defined\n' \
                    'for the bank account: %s\n' \
                    'on the partner: %s\n' \
                    'on line: %s') % (pline.bank_id.state, pline.partner_id.name, pline.name))

        v['partner_bank_iban'] =  pline.bank_id.acc_number or False
        v['partner_bank_number'] =  pline.bank_id.acc_number  \
                and pline.bank_id.acc_number.replace('.','').replace('-','') \
                or  False
        v['partner_post_number']=  pline.bank_id.post_number \
                and pline.bank_id.post_number.replace('.', '').replace('-', '') \
                or  False
        v['partner_bvr'] = pline.bank_id.post_number or ''
        if v['partner_bvr']:
            is_9_pos_adherent = None
            # if adherent bvr number is a 9 pos number
            # add 0 to fill 2nd part plus remove '-'
            # exemple: 12-567-C becomes 12000567C
            if _is_9_pos_bvr_adherent(v['partner_bvr']):
                parts = v['partner_bvr'].split('-')
                parts[1] = parts[1].rjust(6, '0')
                v['partner_bvr'] = ''.join(parts)
                is_9_pos_adherent = True
            # add 4*0 to bvr adherent number with 5 pos
            # exemple: 12345 becomes 000012345
            elif len(v['partner_bvr']) == 5:
                v['partner_bvr'] = v['partner_bvr'].rjust(9, '0')
                is_9_pos_adherent = False
            else:
                raise osv.except_osv(_('Error'),
                                     _('Wrong postal number format.\n'
                                       'It must be 12-123456-9 or 12345 format'))

        if pline.bank_id.bank:
            v['partner_bank_city'] = pline.bank_id.bank.city or False
            v['partner_bank_street'] = pline.bank_id.bank.street or ''
            v['partner_bank_zip'] = pline.bank_id.bank.zip or ''
            v['partner_bank_country'] = pline.bank_id.bank.country and \
                    pline.bank_id.bank.country.name or ''

        v['partner_bank_code'] = pline.bank_id.bank.bic
        v['reference'] = pline.move_line_id.ref
        # Add support for owner of the account if exists..
        if pline.bank_id.owner_name:
            v['partner_name'] = pline.bank_id.owner_name
        else:
            v['partner_name'] = pline.partner_id and pline.partner_id.name or ''

        if pline.partner_id and pline.partner_id.address \
                and pline.partner_id.address[0]:
            v['partner_street'] = pline.partner_id.address[0].street
            v['partner_city'] = pline.partner_id.address[0].city
            v['partner_zip'] = pline.partner_id.address[0].zip
            # If iban => country=country code for space reason
            elec_pay = pline.bank_id.state #Bank type
            if elec_pay == 'iban':
                v['partner_country']= pline.partner_id.address[0].country_id \
                        and pline.partner_id.address[0].country_id.code+'-' \
                        or ''
            else:
                v['partner_country']= pline.partner_id.address[0].country_id \
                        and pline.partner_id.address[0].country_id.name \
                        or ''
        else:
            v['partner_street'] =''
            v['partner_city']= ''
            v['partner_zip']= ''
            v['partner_country']= ''
            raise osv.except_osv('Error', 'No address defined \n' \
                    'for the partner: ' + pline.partner_id.name + '\n' \
                    'on line: ' + pline.name)

        if pline.order_id.date_scheduled:
            date_value = datetime.strptime(pline.order_id.date_scheduled, '%Y-%m-%d')
        elif pline.date:
            date_value = datetime.strptime(pline.date, '%Y-%m-%d')
        else:
            date_value = datetime.now()
        v['date_value'] = date_value.strftime("%y%m%d")

        # si compte iban -> iban (836)
        # si payment structure  -> bvr (826)
        # si non -> (827)

        if elec_pay == 'iban':
            # If iban => country=country code for space reason
            v['comp_country'] = co_addr.country_id and co_addr.country_id.code+'-' or ''
            record_type = record_gt836
            if not v['partner_bank_iban']:
                raise osv.except_osv(_('Error'), _('No IBAN defined \n' \
                        'for the bank account: %s\n' + \
                        'on line: %s') % (res_partner_bank_obj.name_get(cr, uid, [pline.bank_id.id], context)[0][1] , pline.name))

            if v['partner_bank_code'] : # bank code is swift (BIC address)
                v['option_id_bank']= 'A'
                v['partner_bank_ident']= v['partner_bank_code']
            elif v['partner_bank_city']:

                v['option_id_bank']= 'D'
                v['partner_bank_ident']= v['partner_bank_name'] \
                        + ' ' + v['partner_bank_street'] \
                        + ' ' + v['partner_bank_zip'] \
                        + ' ' + v['partner_bank_city'] \
                        + ' ' + v['partner_bank_country']
            else:
                raise osv.except_osv(_('Error'), _('You must provide the bank city '
                        'or the bic code for the partner bank: \n %d\n' + \
                        'on line: %s') %(res_partner_bank_obj.name_get(cr, uid, [pline.bank_id.id], context)[0][1], pline.name))

        elif elec_pay == 'bvrbank' or elec_pay == 'bvrpost':
            from tools import mod10r
            if not v['reference']:
                raise osv.except_osv(_('Error'), 
                                     _('You must provide ' \
                                       'a BVR reference number \n' \
                                       'for the line: %s') % pline.name)
            v['reference'] = v['reference'].replace(' ', '')
            if is_9_pos_adherent:
                if len(v['reference']) > 27: 
                    raise osv.except_osv(_('Error'),
                                         _('BVR reference number is not valid \n' 
                                           'for the line: %s. \n'
                                           'Reference is too long.') % pline.name)
                # do a mod10 check
                if mod10r(v['reference'][:-1]) != v['reference']:
                    raise osv.except_osv(_('Error'),
                                         _('BVR reference number is not valid \n'
                                           'for the line: %s. \n'
                                           'Mod10 check failed') % pline.name)
                # fill reference with 0
                v['reference'] = v['reference'].rjust(27, '0')
            else:
                # reference of BVR adherent with 5 positions number
                # have 15 positions references
                if len(v['reference']) > 15:
                    raise osv.except_osv(_('Error'),
                                         _('BVR reference number is not valid \n'
                                           'for the line: %s. \n'
                                           'Reference is too long '
                                           'for this type of beneficiary.') % pline.name)
                # complete 15 first digit with 0 on left and complete 27 digits with trailing spaces
                # exemple: 123456 becomes 00000000012345____________
                v['reference'] = v['reference'].rjust(15, '0').ljust(27, ' ')

            if not v['partner_bvr']:
                raise osv.except_osv(_('Error'), _('You must provide a BVR number\n'
                    'for the bank account: %s' \
                    'on line: %s') % (res_partner_bank_obj.name_get(cr, uid, [pline.bank_id.id],context)[0][1] ,pline.name))
            record_type = record_gt826

        elif elec_pay == 'bvbank':
            if not v['partner_bank_number'] :
                raise osv.except_osv(_('Error'), _('You must provide ' \
                        'a bank number \n' \
                        'for the partner bank: %s\n' \
                        'on line: %s') % (res_partner_bank_obj.name_get(cr, uid, [pline.bank_id.id], context)[0][1] , pline.name))
            if not  v['partner_bank_clearing']:
                raise osv.except_osv(_('Error'), _('You must provide ' \
                        'a Clearing Number\n' \
                        'for the partner bank: %s\n' \
                        'on line %s') % (res_partner_bank_obj.name_get(cr, uid, [pline.bank_id.id], context)[0][1] , pline.name))
            v['partner_bank_number'] = '/C/'+v['partner_bank_number']
            record_type = record_gt827
        elif elec_pay == 'bvpost':
            if not v['partner_post_number']:
                raise osv.except_osv(_('Error'), _('You must provide ' \
                        'a post number \n' \
                        'for the partner bank: %s\n' \
                        'on line: %s') % (res_partner_bank_obj.name_get(cr, uid, [pline.bank_id.id], context)[0][1] ,pline.name))
            v['partner_bank_clearing']= ''
            v['partner_bank_number'] = '/C/'+v['partner_post_number']
            record_type = record_gt827
        else:
            raise osv.except_osv(_('Error'), _('The Bank type %s of the bank account: %s is not supported') \
                    % (elec_pay, res_partner_bank_obj.name_get(cr, uid, [pline.bank_id.id], context)[0][1],))

        dta_line = record_type(v).generate()

        dta = dta + dta_line
        amount_tot += pline.amount
        amount_currency_tot += pline.amount_currency
        seq += 1

    # segment total
    v['amount_total'] = str(amount_currency_tot).replace('.',',')
    v['sequence'] = str(seq).rjust(5).replace(' ','0')
    if dta :
        dta = dta + record_gt890(v).generate()
    dta_data = _u2a(dta)
    dta_data = base64.encodestring(dta)
    payment_obj.set_done(cr, uid, [data['id']], context)
    attachment_obj.create(cr, uid, {
        'name': 'DTA%s'%time.strftime("%Y-%m-%d_%H:%M:%S", time.gmtime()),
        'datas': dta_data,
        'datas_fname': 'DTA%s.txt'%time.strftime("%Y-%m-%d_%H:%M:%S", time.gmtime()),
        'res_model': 'payment.order',
        'res_id': data['id'],
        }, context=context)
    return dta_data

class create_dta_wizard(osv.osv_memory):
    _name="create.dta.wizard"

    _columns={
        'dta_file':fields.binary('DTA File', readonly=True)
    }
    def create_dta(self, cr, uid, ids, context=None):
        if not context:
            context = {}
        if isinstance(ids, list):
            req_id = ids[0]
        else:
            req_id = ids
        current = self.browse(cr, uid, req_id, context)
        data = {}
        active_ids = context.get('active_ids', [])
        active_id = context.get('active_id', [])
        data['form'] = {}
        data['ids'] = active_ids
        data['id'] = active_id
        dta_file = _create_dta(self, cr, uid, data, context)
        current.write({'dta_file': dta_file})
        return True

create_dta_wizard()

# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
