# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2002-2010 Nexedi SA and Contributors. All Rights Reserved.
#                Aurélien Calonne <aurel@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly adviced to contract a Free Software
# Service Company
#
# 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.
#
# 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 General Public License for more details.
#
# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
##############################################################################

from App.Extensions import getBrain
from Acquisition import Explicit, aq_base
from lxml import etree
from zLOG import LOG, ERROR, INFO
from base64 import b16encode, b16decode

SEPARATOR = '\n'

TioSafeBrain = getBrain('TioSafeBrain', 'TioSafeBrain', reload=1)
TransactionBrain = getBrain('TioSafeBrain', 'Transaction', reload=1)

class MagentoBrain(TioSafeBrain):

  def _setArrowTag(self, document=None, xml=None, source_tag='source',
      destination_tag='destination', category=''):
    """ This method build the XML of the arrow. """
    # only work on the data of the document and not on its parent's data
    document = aq_base(document)
    sync_list = self.getTioSafeSynchronizationObjectList(object_type="Person")

    # create the arrow tag and set if exist source and destination
    arrow = etree.SubElement(xml, 'arrow', type=category)
    arrow_list = [(source_tag, 'source'), (destination_tag, 'destination')]
    for prop, tag in arrow_list:
      if getattr(document, prop, None) is not None:
        movement = etree.SubElement(arrow, tag)
        object_id = getattr(document, prop)
        if object_id != 'default_node':
          for sync in sync_list:
            try:
              #brain_node = sync.getObjectFromId(object_id)
              #node_gid = brain_node.getGid()
              node_gid = object_id
              break
            except (ValueError, AttributeError):
              # This is not a document, might be a category, or the gid previously built
              # pass it anyway in xml
              node_gid = None

          if node_gid is None:
            node_gid = self.getDefaultUnknownNodeGID()
          movement.text = node_gid
        else:
          movement.text = self.getDefaultOrganisationGID()


class MagentoTransaction(MagentoBrain):
  """
    This class allows to build the TioSafe XML of a Sale Order and to sync.
  """
  __allow_access_to_unprotected_subobjects__ = 1

  def __init__(self, object_type, context, **kw):
    self.source = "default_node"
    self.source_ownership = "default_node"
    self.source_decision = "default_node"
    self.source_administration = "default_node"
    MagentoBrain.__init__(self, object_type, context, **kw)

  def getVATCategory(self, vat_value):
    """
    This returns the VAT category according to the value set
    """
    # XXX-AUREL this must be cached for performance reason
    vat_dict = {}
    trade_condition = self.getIntegrationSite().getDefaultSourceTradeValue()

    while len(trade_condition.contentValues(portal_type="Trade Model Line")) == 0:
      # Must find a better way to browse specialised objects
      trade_condition = trade_condition.getSpecialiseValue()
      if trade_condition is None or trade_condition.getPortalType() == "Business Process":
        return None

    for vat_line in trade_condition.contentValues(portal_type="Trade Model Line"):
      #LOG("browsing line %s" %(vat_line.getPath(), 300, "%s" %(vat_line.getBaseApplicationList(),)))
      for app in vat_line.getBaseApplicationList():
        if "base_amount/trade/base/taxable/vat/" in app:
          vat_dict["%.2f" %(vat_line.getPrice()*100.)] = app.split('/')[-1]
    LOG("vat_dict is %s" %(vat_dict), 300, "")
    return vat_dict["%.2f" %(float(vat_value))]

  def _setPaymentMode(self, txn):
    """
    Define the payment mode of a transaction
    This must be the category payment_mode/XXX
    """
    if getattr(self, 'payment_mode', None) is not None:
      payment_mapping = self.getIntegrationSite().getCategoryFromMapping(
        category='Payment Mode/%s' % getattr(self, 'payment_mode'),
        create_mapping=True,
        create_mapping_line=True,
        )
      element = etree.SubElement(txn, 'payment_mode')
      element.text = payment_mapping.split('/', 1)[-1]

  def _asXML(self):
    transaction_type = self.context.getDestinationObjectType()
    transaction = etree.Element('transaction', type=transaction_type)
    tiosafe_sync_list = self.getTioSafeSynchronizationObjectList(object_type='Product')
    erp5_sync_list = self.getERP5SynchronizationObjectList(object_type='Product')
    integration_site = self.getIntegrationSite()

    # marker for checking property existency
    MARKER = object()

    # list of possible tags for a sale order
    tag_list = (
        'title', 'start_date', 'stop_date', 'reference', 'currency',
    )
    self._setTagList(self, transaction, tag_list)
    self._setTagList(self, transaction, ['category', ], SEPARATOR)
    # set arrow list
    try:
      self._setPaymentMode(transaction)
      self._setArrowTagList(self, transaction)
    except ValueError:
      # A mapping must be missing
      return None

    # order the movement list
    movement_list = []

    # build a list of 2-tuple
    # the first key contains the sort element
    # the second part of the tuple contains a dict which contains all the data
    # of the transaction line
    method_id = self.getPortalType().replace(' ', '')
    portal_type = self.getPortalType().replace(' ', '_').lower()
    line_type_list = ['', 'Delivery', 'Discount', ]

    module_id = "%s_module" %(portal_type)
    module = getattr(integration_site, module_id)

    for line_type in line_type_list:
      getter_line_method = getattr(
          module,
          'get%s%sLineList' % (line_type, method_id),
          MARKER,
      )
      if getter_line_method is not MARKER:
        # browse each transaction lines, build the sort element and set data
        parameter_kw = {'%s_id' % portal_type: str(self.getId()), }
        for line in getter_line_method(**parameter_kw):
          key_list = ['title', 'resource', 'reference', 'quantity', 'price']
          value_list = [getattr(line, x, '') for x in key_list]
          movement_dict = {'context': line,}
          # set to None the '' value of the list
          for k, v in zip(key_list, value_list):
            movement_dict[k] = v or None

          # set the VAT value
          movement_dict['VAT'] = self.getVATCategory(getattr(line, 'vat', None))

          # the following boolean var allows to check if variation will be sync
          variation_sync = True

          # retrieve the resource and the gid in the line
          if not line_type:
            # work on transaction lines
            for tiosafe_sync in tiosafe_sync_list:
              try:
                # FIXME: Is it always product_id give as parameter ?
                brain_node = tiosafe_sync.getObjectFromId(line.product_id)
                resource_gid = brain_node.getGid()
                break
              except (ValueError, AttributeError):
                resource_gid = None

            if resource_gid is None:
              resource_gid = self.getDefaultUnknownResourceGID()

            for erp5_sync in erp5_sync_list:
              try:
                resource = erp5_sync.getObjectFromGid(b16encode(resource_gid))
                break
              except (ValueError, AttributeError):
                resource = None
                # do not sync variations with the Unknown product
                variation_sync = False
          else:
            # do not sync variations with delivery or discount
            variation_sync = False
            # work on delivery and discount transaction lines
            resource_gid = resource_id = line.resource
            try:
              brain_node = tiosafe_sync.getObjectFromId(resource_id)
              resource_gid = brain_node.getGid()
            except (ValueError, AttributeError):
              # case of default delivery/discount
              if not 'Service' in resource_id:
                resource_gid = ' Unknown'
              LOG(
                  'Transaction, getting resource failed',
                  300,
                  'resource_id = %s, remains %s' % (resource_id, resource_gid),
              )
              pass
            # through the type render the delivery or the discount
            if line_type == 'Discount':
              resource = integration_site.getSourceCarrierValue()
            elif line_type == 'Delivery':
              resource = integration_site.getDestinationCarrierValue()
            else:
              raise ValueError, 'Try to work on "%s" which is an invalid type.' % line_type
          # after the work on the line set the resource value which will be
          # render in the xml
          movement_dict['resource'] = resource_gid

          # browse line variations and set them to a list
          getter_line_category_method = getattr(
              module,
              'get%sLineCategoryList' % method_id,
              MARKER,
          )
          category_value_list = [getattr(line, 'category', ''), ]
          # FIXME: variation are sync only on line with a product ?
          # else don't sync variations for delivery, discount and Unknown prodcut
          if variation_sync and \
              getter_line_category_method is not MARKER:
            parameter_kw = {
                '%s_id' % portal_type: str(self.getId()),
                '%s_line_id' % portal_type: str(line.getId()),
            }
            for brain in getter_line_category_method(**parameter_kw):
              try:
                category_value_list.append(
                    integration_site.getCategoryFromMapping(
                        brain.category,
                        resource,
                    )
                )
              except ValueError:
                return None

          # sort categories, build the sort key and set categoires in the dict
          if len(category_value_list):
            category_value_list.sort()
            movement_dict['category'] = category_value_list
          # build the element which allows to sort
          movement_list.append(movement_dict)

    # Sort the movement list for fix point
    def cmp_resource(a,b):
      a_str = "%s %s %s" %(a['resource'], a['title'], ' '.join(a['category']))
      b_str = "%s %s %s" %(b['resource'], b['title'], ' '.join(b['category']))
      return cmp(a_str, b_str)

    movement_list.sort(cmp=cmp_resource)

    # the second part build the XML of the transaction
    # browse the ordered movement list and build the movement list as a result
    # the xml through of the line data in the dict
    for movement_dict in movement_list:
      movement = etree.SubElement(transaction, 'movement')
      # set arrow list on the movement
      self._setArrowTagList(movement_dict['context'], movement)
      # if exist the following tags in the line dict, add them in the xml
      tag_list = ('resource', 'title', 'reference', 'quantity', 'price', 'VAT')
      for tag in tag_list:
        if tag in movement_dict:
          element = etree.SubElement(movement, tag)
          element.text = movement_dict[tag]
      # add the categories to the movement
      for category_value in movement_dict['category']:
        if len(category_value):
          category = etree.SubElement(movement, 'category')
          category.text = category_value

    xml = etree.tostring(transaction, pretty_print=True, encoding='utf-8')
    LOG("Transactions asXML returns : %s" % (xml, ), 300, "")
    return xml