# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2009 Nexedi SA and Contributors. All Rights Reserved.
#               Hervé Poulain <herve@nexedi.com>
#               Mayoro DIAGNE <mayoro@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability 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
# garantees 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################

from Products.CMFCore.WorkflowCore import WorkflowException
from Products.ERP5SyncML.Document.Conflict import Conflict
from Products.ERP5TioSafe.Conduit.TioSafeBaseConduit import TioSafeBaseConduit
from Products.ERP5TioSafe.Conduit.ERP5NodeConduit import ERP5NodeConduit
from Products.ERP5SyncML.SyncMLConstant import XUPDATE_ELEMENT,\
     XUPDATE_INSERT_OR_ADD_LIST, XUPDATE_DEL, XUPDATE_UPDATE
from DateTime import DateTime
from lxml import etree
from zLOG import LOG, INFO, ERROR
from base64 import b16encode

DEBUG=False

ADDRESS_TAG_LIST = ['street', 'zip', 'city', 'country']
BILLING_TAG_LIST = ["fax", "phone", "cellphone"]
parser = etree.XMLParser(remove_blank_text=True)

class UbercartNodeConduit(ERP5NodeConduit):
  """
    This is the conduit use to synchonize ERP5 Persons from Ubercart
  """

  def applyXupdate(self, object=None, xupdate=None, previous_xml=None, request_parameter_dict=None,**kw):
    """ Parse the xupdate and then it will call the conduit. """
    conflict_list = []
    request_parameter_dict = {}
    if isinstance(xupdate, (str, unicode)):
      xupdate = etree.XML(xupdate, parser=parser)
    if kw.get('conduit', None) is not None:
      if request_parameter_dict is None:
        request_parameter_dict = {'person_id': object.getId(), }
      if not request_parameter_dict.has_key("person_id"):
        request_parameter_dict['person_id'] = object.getId()
      for subnode in xupdate:
        sub_conflict_list, sub_param_dict = self.updateNode(xml=self.getContextFromXpath(subnode, subnode.get('select')),
                                                            object=object,
                                                            previous_xml=previous_xml,
                                                            request_parameter_dict=request_parameter_dict,
                                                            **kw
                                                            )
        conflict_list += sub_conflict_list
        request_parameter_dict.update(sub_param_dict)

    if len(request_parameter_dict):
      if not request_parameter_dict.has_key('billing_address_country'):
        # Always include country so that it does not get remove automatically
        request_parameter_dict["billing_address_country"] = object.country

      # Once we got everything, call web service request
      LOG("calling update", 300, request_parameter_dict)
      object.context.person_module.updatePerson(**request_parameter_dict)
      # Update the brain as xml put in signature is generating from it
      new_document = object.context.person_module[object.getId()]
      object.updateProperties(new_document)
    LOG("returning the conflict_list", 300, [x.__dict__ for x in conflict_list])
    return conflict_list


  def updateNode(self, xml=None, object=None, previous_xml=None, force=False,
      simulate=False, reset=False, xpath_expression=None, request_parameter_dict=None, **kw):
    """
      This method browse the xml which allows to update data and update the
      correpsonging object.
    """
    conflict_list = []
    if simulate or xml is None:
      return conflict_list

    xml = self.convertToXml(xml)
    # we have an xupdate xml
    if xml.xpath('name()') == 'xupdate:modifications':
      conflict_list += self.applyXupdate(
          object=object,
          xupdate=xml,
          conduit=self,
          previous_xml=previous_xml,
          force=force,
          simulate=simulate,
          reset=reset,
          request_parameter_dict=request_parameter_dict,
          **kw
      )
      return conflict_list
    # we may have only the part of an xupdate
    else:
      # previous_xml is required as an etree type
      if type(previous_xml) == str:
        previous_xml = etree.XML(previous_xml, parser=parser)

      if self.isProperty(xml):
        xpath = xml.xpath('name()')
        # XUPDATE_UPDATE -> update data or sub-object
        if xpath in XUPDATE_UPDATE:
          sub_conflict_list, sub_param_dict = self._updateXupdateUpdate(
              document=object,
              xml=xml,
              previous_xml=previous_xml,
              request_parameter_dict=request_parameter_dict,
              **kw
          )
        # XUPDATE_DEL -> delete data or sub-object
        elif xpath in XUPDATE_DEL:
          sub_conflict_list, sub_param_dict = self._updateXupdateDel(
              document=object,
              xml=xml,
              previous_xml=previous_xml,
              request_parameter_dict=request_parameter_dict,
              **kw
          )
        # XUPDATE_INSERT_OR_ADD_LIST -> add data or sub-object
        elif xpath in XUPDATE_INSERT_OR_ADD_LIST:
          sub_conflict_list, sub_param_dict = self._updateXupdateInsertOrAdd(
              document=object,
              xml=xml,
              previous_xml=previous_xml,
              request_parameter_dict=request_parameter_dict,
              **kw
          )
        conflict_list += sub_conflict_list

      return conflict_list, sub_param_dict


  def _createContent(self, xml=None, object=None, object_id=None, sub_object=None,
      reset_local_roles=0, reset_workflow=0, simulate=0, **kw):
    """ We are not suppose to create new person into the plugin """
    LOG("_createContent", 300, "XXX")
    return None

  def _deleteContent(self, object=None, object_id=None):
    """ We do not delete person """
    raise NotImplementedError

  def _updateXupdateUpdate(self, document=None, xml=None, previous_xml=None, request_parameter_dict=None, **kw):
    """
      This method is called in updateNode and allows to work on the  update of
      elements.
    """
    conflict_list = []
    xpath_expression = xml.get('select')
    tag = xpath_expression.split('/')[-1]
    value = xml.text

    # retrieve the previous xml etree through xpath
    previous_xml = previous_xml.xpath(xpath_expression)
    try:
      previous_value = previous_xml[0].text
    except IndexError:
      raise IndexError, 'Too little or too many value, only one is required for %s' % (
          previous_xml
      )

    if previous_value is None:
      previous_value = ""

    conflicted = False

    if tag in ADDRESS_TAG_LIST:
      LOG("updating tag %s to %s" %(tag, value), 300, "")
      # There is just one address in oxatis, it is the billing one
      current_value = getattr(document, tag, '')
      if current_value not in [value, previous_value]:
        conflicted = True
      else:
        if tag == "country":
          mapping = document.context.getMappingFromCategory('region/%s' % value)
          value = mapping.split('/', 1)[-1]
          request_parameter_dict['billing_address_country'] = value
        elif tag == 'street':
          request_parameter_dict["billing_address_street"] = value
          # also add lines
          street_lines = value.split('\n')
          i = 1
          for street_line in street_lines:
            request_parameter_dict["billing_address_street_l%s" %(i)] = street_line
            i += 1
        else:
          request_parameter_dict["billing_address_%s" %(tag)] = value

    else:
      # Not specific tags
      current_value = getattr(document, tag, '')
      if current_value not in [value, previous_value]:
        conflicted = True
      # Update tag to specific name
      if tag in BILLING_TAG_LIST:
        tag = "billing_%s" %(tag)
      request_parameter_dict[tag] = value

    # Return conflict if any
    if conflicted:
      conflict = Conflict(
          object_path=document.getPhysicalPath(),
          keyword=tag,
      )
      conflict.setXupdate(etree.tostring(xml, encoding='utf-8'))
      conflict.setLocalValue(current_value)
      conflict.setRemoteValue(value)
      conflict_list.append(conflict)
    LOG("update returns %s / %s" %(conflict_list, request_parameter_dict), 300, "")
    return conflict_list, request_parameter_dict


  def _updateXupdateDel(self, document=None, xml=None, previous_xml=None, request_parameter_dict=None, **kw):
    """ This method is called in updateNode and allows to remove elements. """
    tag = xml.get('select').split('/')[-1]
    
    # specific work for address and address elements
    if tag in ADDRESS_TAG_LIST:
      # remove the corresponding address or the element of the address
      request_parameter_dict["billing_address_%s" %(tag)] = ""
    elif tag in BILLING_TAG_LIST:
      request_parameter_dict["billing_%s" %(tag)] = ""
    else:
      request_parameter_dict[tag] = ''
    LOG("delete returns %s / %s" %([], request_parameter_dict), 300, "")
    return [], request_parameter_dict


  def _updateXupdateInsertOrAdd(self, document=None, xml=None, previous_xml=None, request_parameter_dict=None, **kw):
    """ This method is called in updateNode and allows to add elements. """
    conflict_list = []
    
    for subnode in xml.getchildren():
      tag = subnode.attrib['name']
      value = subnode.text

      if tag == 'address':
        # We create a new address so telephones & fax must be resetted
        for tag in BILLING_TAG_LIST:
          if not request_parameter_dict.has_key("billing_%s" %(tag)):
            request_parameter_dict["billing_%s" %(tag)] = ""
        for subsubnode in subnode.getchildren():
          if subsubnode.tag == 'country':
            # through the mapping retrieve the country
            value = document.context.getMappingFromCategory(
                'region/%s' % subsubnode.text,
            ).split('/', 1)[-1]
          else:
            value = subsubnode.text
          request_parameter_dict["billing_address_%s" %(subsubnode.tag)] = value
      elif tag in ADDRESS_TAG_LIST:
        # We only have one address in oxatis
        if tag == 'country':
          # through the mapping retrieve the country
          mapping = document.context.getMappingFromCategory('region/%s' % value)
          value = mapping.split('/', 1)[-1]
        request_parameter_dict["billing_address_%s" %(tag)] = value
      elif tag in BILLING_TAG_LIST:
        tag = "billing_%s" %(tag,)
        request_parameter_dict[tag] = value
      else:
        request_parameter_dict[tag] = value
    LOG("insert returns %s / %s" %(conflict_list, request_parameter_dict), 300, "")
    return conflict_list, request_parameter_dict