## $Id: batching.py 7271 2011-12-04 18:00:38Z henrik $
##
## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
## 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
##
"""Batch processing for applicants.
"""
import csv
import grok
from zope.schema import getFields
from zope.interface import Interface
from zope.component import queryUtility
from zope.catalog.interfaces import ICatalog
from waeup.sirp.interfaces import (
    IBatchProcessor, IObjectConverter, FatalCSVError)
from waeup.sirp.utils.batching import BatchProcessor
from waeup.sirp.applicants.interfaces import (
    IApplicantsContainer, IApplicant, IApplicantUpdateByRegNo)

class ApplicantsContainerImporter(BatchProcessor):
    """An importer for applicants containers.
    """
    grok.implements(IBatchProcessor)
    grok.provides(IBatchProcessor)
    grok.context(Interface)
    util_name = 'applicants container importer'
    grok.name(util_name)

    name = u'Applicants Container Importer'
    mode = u'create'
    iface = IApplicantsContainer

    location_fields = ['code',]
    factory_name = 'waeup.ApplicantsContainer'

    def parentsExist(self, row, site):
        return 'applicants' in site.keys()

    def entryExists(self, row, site):
        return row['code'] in site['applicants'].keys()

    def getParent(self, row, site):
        return site['applicants']

    def getEntry(self, row, site):
        if not self.entryExists(row, site):
            return None
        parent = self.getParent(row, site)
        return parent.get(row['code'])

    def addEntry(self, obj, row, site):
        parent = self.getParent(row, site)
        parent[row['code']] = obj
        return

    def delEntry(self, row, site):
        parent = self.getParent(row, site)
        del parent[row['code']]
        return

class ApplicantImporter(BatchProcessor):
    """A batch processor for IApplicant objects.
    """
    grok.implements(IBatchProcessor)
    grok.provides(IBatchProcessor)
    grok.context(Interface)
    util_name = 'applicantimporter'
    grok.name(util_name)
    name = u'Applicant Importer'
    iface = IApplicant
    location_fields = []
    factory_name = 'waeup.Applicant'
    location_fields = ['container_code']

    mode = None

    @property
    def available_fields(self):
        return sorted(list(set(
            ['application_number','reg_number','container_code'] + getFields(
                self.iface).keys())))

    def checkHeaders(self, headerfields, mode='create'):
        if not 'reg_number' in headerfields and not 'application_number' \
            in headerfields:
            raise FatalCSVError(
                "Need at least columns application_number or reg_number ")
        if mode == 'create':
            for field in self.required_fields:
                if not field in headerfields:
                    raise FatalCSVError(
                        "Need at least columns %s for import!" %
                        ', '.join(["'%s'" % x for x in self.required_fields]))
        # Check for fields to be ignored...
        not_ignored_fields = [x for x in headerfields
                              if not x.startswith('--')]
        if len(set(not_ignored_fields)) < len(not_ignored_fields):
            raise FatalCSVError(
                "Double headers: each column name may only appear once.")
        return True

    def getLocator(self, row):
        #if 'application_number' in row.keys() and row['application_number']:
        if row.get('application_number',None):
            return 'application_number'
        elif 'reg_number' in row.keys() and row['reg_number']:
            return 'reg_number'
        else:
            return None

    def getParent(self, row, site):
        if not 'applicants' in site.keys():
            return False
        return site['applicants'][row['container_code']]

    def parentsExist(self, row, site):
        return self.getParent(row, site) is not None

    def getEntry(self, row, site):
        if not self.parentsExist(row, site):
            return None
        parent = self.getParent(row, site)
        if self.getLocator(row) == 'application_number':
            if row['application_number'] in parent:
                applicant = parent[row['application_number']]
                return applicant
        elif self.getLocator(row) == 'reg_number':
            reg_number = row['reg_number']
            cat = queryUtility(ICatalog, name='applicants_catalog')
            results = list(
                cat.searchResults(reg_number=(reg_number, reg_number)))
            if results:
                return results[0]
        return None

    def entryExists(self, row, site):
        return self.getEntry(row, site) is not None

    def addEntry(self, obj, row, site):
        parent = self.getParent(row, site)
        parent.addApplicant(obj)
        return

    def delEntry(self, row, site):
        applicant = self.getEntry(row, site)
        if applicant is not None:
            parent = self.getParent(row, site)
            del parent[applicant.application_number]
        pass

    def getMapping(self, path, headerfields, mode):
        """Get a mapping from CSV file headerfields to actually used fieldnames.
        """
        result = dict()
        reader = csv.reader(open(path, 'rb'))
        raw_header = reader.next()
        for num, field in enumerate(headerfields):
            if field not in ['container_code',
                'application_number', 'reg_number'] and mode == 'remove':
                continue
            if field == u'--IGNORE--':
                # Skip ignored columns in failed and finished data files.
                continue
            result[raw_header[num]] = field
        return result

    def checkConversion(self, row, mode='create'):
        """Validates all values in row.
        """
        if mode in ['update', 'remove']:
            if self.getLocator(row) == 'reg_number':
                iface = IApplicantUpdateByRegNo
        else:
            iface = self.iface
        converter = IObjectConverter(iface)
        errs, inv_errs, conv_dict =  converter.fromStringDict(
            row, self.factory_name)
        return errs, inv_errs, conv_dict
