"""

Utility functions for use throughout DTA Anyway.

"""
__copyright__   = "Copyright 2011 SFCTA"
__license__     = """
    This file is part of DTA.

    DTA 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 3 of the License, or
    (at your option) any later version.

    DTA 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 DTA.  If not, see <http://www.gnu.org/licenses/>.
"""
import copy
import re
import sys
import datetime

import dta
import shapefile

from itertools import izip
from collections import defaultdict
import xml.etree.ElementTree as ET
from xml.dom.minidom import parseString

def parseTextRecord(iterable, is_separator=re.compile(r"^a"),
                is_comment = re.compile(r"^#"),
                joiner=lambda tokens: " ".join(tokens)):

    """
    Read a text file record by record where a record is defined
    in multiple sequential lines and return a string concatenating
    all the lines of a record into one line.

    * *is_separator* is a regex instance that identifies the lines which separate records.
    * *is_comment* is a regex instance that identifies comment lines that ought to
      be bypassed.
    * *joiner*  is a function used on the list of strings

    Yields the result of *joiner* on the list of strings.
    """

    record = []
    for line in iterable:
        line = line.strip()
        if is_comment.match(line):
            continue
        if is_separator.match(line):
            if record:
                if isinstance(joiner(record), str):
                    if is_separator.match(joiner(record)):
                        yield joiner(record)
                    record = []
                else:
                    yield record
                    record = [] # remove if record headers do
                            # not serve as record separators
            record.append(line)
        else:
            record.append(line)
    if record:
        yield joiner(record)

def militaryTimeToDateTime(militaryTime):
    """
    Return a datetime.time object that corresponds
    to the military time
    """
    mTime = str(militaryTime)
    if len(mTime) == 4:
        hours = int(mTime[:2])
        minutes = int(mTime[2:])
        return datetime.time(hours, minutes)

    elif len(mTime) == 3:
        hours = int(mTime[0])
        minutes = int(mTime[1:])
        return datetime.time(hours, minutes)
    else:
        raise dta.DtaError('Unknown military time format %d' % militaryTime)

def writePoints(iterPoints, fileName):
    """
    Write the input points to a shapefile. Each point should be a tuple of (x, y) floats
    """
    w = shapefile.Writer(shapefile.POINT)
    w.field("ID", "N", 10)
    i = 0
    for x,y in iterPoints:
        i += 1
        w.point(x,y)
        w.record(i)
    w.save(fileName)

def writePolygon(listOfPoints, fileName):
    """
    Write the input points as a polygon. Each point should be a tuple of (x,y) coordinates
    """
    w = shapefile.Writer(shapefile.POLYGON)
    w.field("ID", "N", 10)
    w.poly(parts=[listOfPoints])
    w.record(1)
    w.save(fileName)

def isRightTurn(pi, pj, pk):

    if direction(pi, pj, pk) > 0:
        return True
    return False

def direction(point0, point1, point2):
    """
    Returns the :py:func:`dta.crossProduct` result of the two vectors
    <*point2*-*point0*, *point1*-*point0*>
    e.g. the vectors emanating from point0 to point2 and point1.

    All arguments should be 2-tuple points of floats.
    """
    return crossProduct((point2[0] - point0[0], point2[1] - point0[1]),
                        (point1[0] - point0[0], point1[1] - point0[1]))

def crossProduct(v1, v2):
    """
    Assuming the two vectors *v1* and *v2* are on the z=0 plane, returns
    the z-component of the cross product between *v1* and *v2*.

    The magnitude of this value is the area of the parallelogram with *v1* and *v2* as sides,
    and is therefore 0 when the vectors are collinear.
    """
    return v1[0]*v2[1] - v2[0]*v1[1]

def lineSegmentsCross(p1, p2, p3, p4, checkBoundaryConditions=False):
    """
    Helper function that determines if two line segments,
    defined as a sequence of pairs of points, (*p1*, *p2*) and (*p3*, *p4*) intersect.
    If so it returns True, otherwise False. If the two
    line segments touch each other the method will
    return False.

    If *checkBoundaryConditions* is True, then if one point collinear with the other segment, we return
    True if and only if the point is **on** the other segment.

    If *checkBoundaryConditions* is False, we don't care about this case, and return False.
    """

    d1 = direction(p3, p4, p1)
    d2 = direction(p3, p4, p2)
    d3 = direction(p1, p2, p3)
    d4 = direction(p1, p2, p4)

    if ((d1 > 0 and d2 < 0) or (d1 < 0 and d2 > 0)) and \
            ((d3 > 0 and d4 < 0) or (d3 < 0 and d4 > 0)):
        return True
    if not checkBoundaryConditions:
        return False
    if d1 == 0 and onSegment(p3, p4, p1):
        return True
    elif d2 == 0 and onSegment(p3, p4, p2):
        return True
    elif d3 == 0 and onSegment(p1, p2, p3):
        return True
    elif d4 == 0 and onSegment(p1, p2, p4):
        return True
    return False

def polylinesCross(polyline1, polyline2):
    """
    Return True if the two polylines cross.
    Each polyline is should be a list of two point tuples
    """
    for p1, p2 in izip(polyline1, polyline1[1:]):
        for p3, p4 in izip(polyline2, polyline2[1:]):
            if lineSegmentsCross(p1, p2, p3, p4):
                return True
    if lineSegmentsCross(polyline1[-2], polyline1[-1],
                         polyline2[-2], polyline2[-1],
                         checkBoundaryConditions=True):
        return True
    return False

def onSegment(pi, pj, pk):
    """
    Assuming that point *pk* is known to be collinear with a segment (*pi*, *pj*),
    this function determines if *pk* lies **on** segment (*pi*, *pj*).
    It does so by examining if point *pk* is inside the boundary box of segment (*pi*,*pj*)

    *pi*, *pj*, *pk* are 2-tuples of floats (x,y).
    """
    if min(pi[0], pj[0]) <= pk[0] <= max(pi[0], pj[0]) and \
            min(pi[1], pj[1]) <= pk[1] <= max(pi[1], pj[1]):
        return True
    return False

def getMidPoint(p1, p2):
    """
    Return the the point in the middle of p1 and p2 as a (x,y) tuple.
    """
    return ((p1[0] + p2[0]) / 2.0, (p1[1] + p2[1]) / 2.0)

def getReverseNetwork(net):
    """
    Returns a network copy that has all the links reversed
    """
    rNet = dta.Network(net.getScenario())

    for node in net.iterNodes():
        cNode = copy.copy(node)
        cNode._incomingLinks = []
        cNode._outgoingLinks = []
        rNet.addNode(cNode)

    for link in net.iterLinks():
        rLink = dta.Link(link._id,
                     rNet.getNodeForId(link.getEndNode().getId()),
                     rNet.getNodeForId(link.getStartNode().getId()),
                     "")
        rNet.addLink(rLink)

    return rNet

def plotSignalAttributes(net, militaryStartTime, militaryEndTime, outputFile):
    """
    plot signal attributes
    """
    w = shapefile.Writer(shapefile.POINT)

    w.field("ID", "N", 10)
    w.field("Cycle", "N", 10)
    w.field("NumPhas", "N", 10)
    w.field("allRed", "N", 10)
    w.field("yellow", "N", 10)
    w.field("green", "N", 10)
    w.field("minGreen", "N", 10)
    w.field("maxGreen", "N", 10)

    pi = net.getPlanCollectionInfo(militaryStartTime, militaryEndTime)

    for node in net.iterRoadNodes():
        if node.hasTimePlan(pi):
            tp = node.getTimePlan(pi)
            cycle = tp.getCycleLength()
            numPhas = tp.getNumPhases()

            allRed = sum([phase.getRed() for phase in tp.iterPhases()])
            yellow = sum([phase.getYellow() for phase in tp.iterPhases()])
            green = cycle - yellow - allRed

            minGreen = min([phase.getGreen() for phase in tp.iterPhases()])
            maxGreen = max([phase.getGreen() for phase in tp.iterPhases()])

            w.point(node.getX(), node.getY())
            w.record(node.getId(), cycle, numPhas, allRed, yellow, green, minGreen, maxGreen)

    w.save(outputFile)

class MappingError(Exception):
    pass

class NetworkMapping(object):
    """
    Contains the node and link mappings of two network objects
    """
    def __init__(self, netOne, netTwo):
        """
        netOne and netTwo are the two network objects to be mapped
        """
        self._netOne = netOne
        self._netTwo = netTwo

        self._mapNodesOneToTwo = dict()
        self._mapNodesTwoToOne = dict()
        self._mapLinksOneToTwo = defaultdict(dict)
        self._mapLinksTwoToOne = defaultdict(dict)

    def mapNodesById(self):
        """
        Map the nodes of the two objects based on node ids
        """
        for nodeOne in self._netOne.iterNodes():
            if self._netTwo.hasNodeForId(nodeOne.getId()):
                nodeTwo = self._netTwo.getNodeForId(nodeOne.getId())
                self.setMappedNode(nodeOne, nodeTwo)

    def mapLinksByOrientation(self, maxAngle):
        """
        Map the links based on the input maxAngle for all the
        pair of nodes that have already been mapped
        """
        def getMinAngle(node1, edge1, node2, edge2):
            """
            Returns a positive number in degrees always in [0, 180]
            that corresponds to the
            acute angle between the two edges
            """
            orientation1 = node1.getOrientation(edge1.getMidPoint())
            orientation2 = node2.getOrientation(edge2.getMidPoint())
            if orientation2 > orientation1:
                angle1 = orientation2 - orientation1
                angle2 = 360 - orientation2 + orientation1
                return min(angle1, angle2)
            elif orientation1 > orientation2:
                angle1 = orientation1 - orientation2
                angle2 = 360 - orientation1 + orientation2
                return min(angle1, angle2)
            else:
                return 0

        for nodeOne, nodeTwo in self._mapNodesOneToTwo.iteritems():
            #incoming edges
            edges2 = list(nodeTwo.iterIncomingEdges())
            for edge1 in nodeOne.iterIncomingEdges():
                #pick the closest
                if len(edges2) == 0:
                    break
                edges2 = sorted(edges2, key = lambda edge2: getMinAngle(nodeOne, edge1, nodeTwo, edge2))
                closestEdge = edges2[0]

                if getMinAngle(nodeOne, edge1, nodeTwo, closestEdge) < maxAngle:
                    self.setMappedLink(nodeOne, edge1, nodeTwo, closestEdge)
                    edges2.pop(0)

            #outgoing edges
            edges2 = list(nodeTwo.iterOutgoingEdges())
            for edge1 in nodeOne.iterOutgoingEdges():
                if len(edges2) == 0:
                    break
                edges2 = sorted(edges2, key = lambda edge2: getMinAngle(nodeOne, edge1, nodeTwo, edge2))
                closestEdge = edges2[0]
                if getMinAngle(nodeOne, edge1, nodeTwo, closestEdge) < maxAngle:
                    self.setMappedLink(nodeOne, edge1, nodeTwo, closestEdge)
                    edges2.pop(0)

    def setMappedNode(self, nodeOne, nodeTwo):
        """
        Map the two input nodes to each other. A one to one mapping is
        being created for the two nodes.
        """
        if nodeOne in self._mapNodesOneToTwo:
            raise DtaError("Node one %s has already been mapped to a node"
                               % nodeOne.id)
        if nodeTwo in self._mapNodesTwoToOne:
            raise DtaError("Node two %s has already been mapped to a node"
                               % nodeTwo.id)

        self._mapNodesOneToTwo[nodeOne] = nodeTwo
        self._mapNodesTwoToOne[nodeTwo] = nodeOne
        #TODO: consider
        #self._mapLinksOneToTwo[nodeOne] = {}

    def getMappedNode(self, node):
        """
        Return the mapped node. If the input node is from networkOne the
        corresponding node from network two is being returned and vice
        versa
        """
        if isinstance(node, self._netOne.getNodeType()):
            if node not in self._mapNodesOneToTwo:
                raise DtaError("Node %s does not have a mapped node" % node.id)
            return self._mapNodesOneToTwo[node]
        elif isinstance(node, self._netTwo.getNodeType()):
            if node not in self._mapNodesTwoToOne:
                raise DtaError("Node %s does not have a mapped node" % node.id)
            return self._mapNodesTwoToOne[node]
        else:
            raise DtaError("Node %s should belong to one of the mapped networks"
                               % node.id)

    def setMappedLink(self, nodeOne, linkOne, nodeTwo, linkTwo):
        """
        Map linkOne attached to nodeOne to linkTwo attached to nodeTwo
        """
        assert isinstance(nodeOne, self._netOne.getNodeType())
        assert isinstance(linkOne, self._netOne.getLinkType())

        assert isinstance(nodeTwo, self._netTwo.getNodeType())


# Opus/UrbanSim urban simulation software.
# Copyright (C) 2010-2011 University of California, Berkeley, 2005-2009 University of Washington
# See opus_core/LICENSE

import numpy
from numpy import array, asarray, arange, zeros, ones, concatenate, sum, resize
from numpy import where, equal, ndarray, clip, sort, searchsorted, cumsum
from numpy import float32, float64, newaxis, rank, take, ma, argmax
from numpy import logical_not
from opus_core.misc import ncumsum, is_masked_array, unique, ismember
from opus_core.logger import logger
from numpy.random import uniform, randint

def sample_replace(source_array, size, return_index=False):
    """Equal probability sampling; with-replacement case
    if return_index is True, return indices to source_array,
    otherwise, return elements in source_array
    """
    min = 0; max = source_array.size
    if return_index:
        return randint(min,max,size)
    else:
        return source_array[randint(min,max,size)]

def sample_noreplace(source_array, size, return_index=False, try_replace=True):
    """equal probability sampling; without replacement case.
    Using numpy functions, efficient for large array"""
    if not isinstance(source_array, ndarray):
        try:
            source_array = asarray(source_array)
        except:
            raise TypeError, "source_array must be of type ndarray"

    n = source_array.size
    if n == 0:
        if return_index:
            return array([], dtype='i')
        else:
            return array([], dtype=source_array.dtype)
    prob_array = resize(array([1.0/n], dtype = float32), n)
    #fake a equal probability array to use probsample_noreplace
    return probsample_noreplace(source_array, size, prob_array,
                                return_index=return_index,
                                try_replace=try_replace)

def probsample_replace(source_array, size, prob_array, return_index=False):
    """Unequal probability sampling; with replacement case.
    Using numpy searchsorted function, suitable for large array"""
    if not isinstance(source_array, ndarray):
        try:
            source_array = asarray(source_array)
        except:
            raise TypeError, "source_array must be of type ndarray"

    if prob_array is None:
        return sample_replace(source_array,size, return_index=return_index)

    if prob_array.sum() == 0:
        raise ValueError, "there aren't non-zero weights in prob_array"

    cum_prob = cumsum(prob_array, dtype='float64')

    sample_prob = uniform(0, cum_prob[-1], size)
    sampled_index = searchsorted(cum_prob, sample_prob)
    sampled_index = sampled_index.astype('int32')
    # due to precision problems, searchsorted could return index = cum_prob.size
    sampled_index = clip(sampled_index, 0, cum_prob.size-1)

    if return_index:
        return sampled_index
    else:
        return source_array[sampled_index]

def probsample_noreplace(source_array, sample_size, prob_array=None,
                         exclude_element=None, exclude_index=None,
                         return_index=False,
                         try_replace=True):
    """generate non-repeat random 1d samples from source_array of sample_size, excluding
    indices appeared in exclude_index.

    return indices to source_array if return_index is true.

    source_array - the source array to sample from
    sample_size - scalar representing the sample size
    prob_array - the array used to weight sample
    exclude_element - array representing elements should not appear in resulted array
    exclude_index - array representing indices should not appear in resulted array,
                    which can be used, for example, to exclude current choice from sampling,
                    indexed to source_array
    """
    if sample_size <= 0:
        #logger.log_warning("sample_size is %s. Nothing is sampled." % sample_size)
        if return_index:
            return array([], dtype='i')
        else:
            return array([], dtype=source_array.dtype)

    if prob_array is None:
        return sample_noreplace(source_array, sample_size, return_index=return_index,
                                try_replace=try_replace)
    else:
        #make a copy of prob_array so we won't change its original value in the sampling process
        prob_array2 = prob_array.copy()
        if exclude_element is not None:
            prob_array2[ismember(source_array, exclude_element)] = 0.0

        if exclude_index is not None:
            index_range = arange(source_array.size, dtype="i")
            if isinstance(exclude_index, numpy.ndarray):
                exclude_index = exclude_index[ismember(exclude_index, index_range)]
                prob_array2[exclude_index] = 0.0
            elif (exclude_index in index_range):
                prob_array2[exclude_index] = 0.0

        nzc = nonzerocounts(prob_array2)
        if nzc == 0:
            raise ValueError, "The weight array contains no non-zero elements. Check the weight used for sampling."
        if nzc < sample_size:
            if try_replace:
                logger.log_warning("The weight array contains %s non-zero elements, less than the sample_size %s. Use probsample_replace. " %
                      (nzc, sample_size))
                return probsample_replace(source_array, sample_size, prob_array=prob_array2, return_index=return_index)
            else:
                logger.log_warning("The are %s eligible elements, less than the sample_size %s. Sample %s. " %
                      (nzc, sample_size, nzc))
                sample_size = nzc
        if nzc == sample_size:
            nonzeroindex = prob_array2.nonzero()[0]
            if return_index:
                return nonzeroindex
            else:
                return source_array[nonzeroindex]

    to_be_sampled = sample_size
    sampled_index = array([], dtype='i')  #initialize sampled_index
    while True:
        proposed_index = probsample_replace(source_array, to_be_sampled, prob_array2, return_index=True)
        valid_index = unique(proposed_index, return_index=False)
        #assert all( logical_not(ismember(valid_index, sampled_index)) )
        #valid_index = valid_index[logical_not(ismember(valid_index, sampled_index))]  #this should not be necessary because we control the prob_array
        sampled_index = concatenate((sampled_index, valid_index))

        to_be_sampled -= valid_index.size
        if to_be_sampled == 0:
            if return_index:
                return sampled_index
            else:
                return source_array[sampled_index]

        prob_array2[proposed_index] = 0.0
        nzc = nonzerocounts(prob_array2)
        assert nzc > 0 #given that we have checked and made sure nonzerocounts(prob_array2)>size, it should not run out before we have enough non-repeat samples

def stratifiedsample(source_array, strata_array,
                     sample_size=1,
                     sample_rate=None, min_size=1,
                     weight_array=None, replace=False,
                     return_index=False):
    """stratified sampling from source_array,

    min_size works with sampling_rate,
    sample max(min_size, sample_rate * number of items in a stratum) from each stratum
    """

    sampled_index = array([], dtype='int32')
    unique_strata = unique(strata_array)
    for this_stratum in unique_strata:
        indices_in_stratum = where(equal(strata_array, this_stratum))[0]
        counts = len(indices_in_stratum)

        if sample_rate is not None:
            stratum_sample_size = max(min_size, int(round(counts * sample_rate)))
        else:
            stratum_sample_size = sample_size

        if counts < stratum_sample_size:
            logger.log_warning( "Warning: there are less counts(%s) than sample_size %s for stratum %s. sample %s instead"
             % (counts, stratum_sample_size, this_stratum, counts))
            stratum_sample_size = counts

        if weight_array is not None:
            stratum_weight_array = weight_array[indices_in_stratum].astype(float32)
            if sum(stratum_weight_array) == 0.0:
                logger.log_warning("Warning: the weight_array for stratum %s are all zeros; it is skipped."
                                       % (this_stratum))
                continue

            prob_array = normalize(stratum_weight_array)
            prob_array_size = nonzerocounts(prob_array)
            if stratum_sample_size > prob_array_size:
                if not replace:
                    logger.log_warning("Warning: there are less non-zero entries(%s) in weight_array than sample_size(%s) for stratum %s; sample with replacement"
                     % (prob_array_size, stratum_sample_size, this_stratum))
                    replace=True
        else:
            stratum_weight_array = None
            prob_array = None

        #logger.log_status(this_stratum, stratum_sample_size)
        if stratum_sample_size > 0:
            if not replace:
                sampled_index = concatenate(
                                        (sampled_index,
                                         probsample_noreplace(indices_in_stratum, stratum_sample_size, prob_array)
                                         ))
            else:
                sampled_index = concatenate(
                                        (sampled_index,
                                         probsample_replace(indices_in_stratum, stratum_sample_size, prob_array)
                                         ))
    if return_index:
        return sampled_index
    else:
        return source_array[sampled_index]

def prob2dsample(source_array, sample_size, prob_array=None, exclude_index=None,
                  replace=False, return_index=False):
    """generate non-repeat random 2d samples from source_array of sample_size, not including
    indices appeared in exclude_index; sample column by column, more efficient when there are more
    rows than columns in sample_size.

    return elements in source_array of shape sample_size.

    source_array - the source array to sample from
    sample_size - tuple representing the sample size with (rows, columns), non-repeat at each row
    exclude_index - array representing indices should not appear in resulted array, used to exclude current choice from sampling
    prob_array - the array used to weight sample
    """

    rows, columns = sample_size
    source_array_size = source_array.size

    if source_array_size <= columns and not replace:
        logger.log_warning("There are less or equal indices (%s) in source_array than the sample_size (%s). Sample %s." %
              (source_array_size, columns, source_array_size))

        if return_index:
            return ones((rows,1), dtype='i') * arange(source_array_size)[newaxis,:]
        else:
            return ones((rows,1), dtype='i') * source_array[newaxis,:]

    if prob_array is None:
        prob_array = ones(source_array_size)

    if not (isinstance(prob_array, ndarray) or is_masked_array(prob_array)):
        raise TypeError, "prob_array must be of type ndarray"

#    prob_array_size = nonzerocounts(prob_array)
#    if prob_array_size <= columns:
#            logger.log_warning( "there are less or equal non-zero weight (%s) in prob_array than the sample_size (%s). Sample %s instead. " %\
#                  (prob_array_size, columns, prob_array_size))
#            return ones((rows,1)) * source_array[nonzero(prob_array)][newaxis,:]

    p_array = prob_array

    p_array_sum = p_array.sum(dtype="float64")
    if not ma.allclose(p_array_sum, 1.0):
        if abs(1.0 - p_array_sum) > 0.01:
            # print this message only if it is a serious difference
            logger.log_warning("prob_array doesn't sum up to 1, and is normalized. Sum: %s" % p_array_sum)
        p_array = p_array / p_array_sum

    cum_prob = ncumsum(p_array)

    sampled_choiceset_index = zeros(sample_size, dtype="int32") - 1    #initialize output

    if not replace:
        if exclude_index is not None:
            if not isinstance(exclude_index, ndarray):
                try:
                    exclude_index = asarray(exclude_index)
                except:
                    raise TypeError, "exclude_index must be of type ndarray"
            if exclude_index.shape[0] <> rows:
                raise ValueError, "exclude_index should have the same number of rows as sample_size[0]"
            if rank(exclude_index) == 1:
                exclude_index = exclude_index[:, newaxis]
            #sampled_choiceset_index = concatenate((exclude_index,sampled_choiceset_index),axis=1)
                      #attach exclude_index to the beginning of sampled_choiceset_index
        else:
            exclude_index = zeros(shape=(sample_size[0],1), dtype="int32")

        for j in range(columns):
            slots_to_be_sampled = arange(rows)
            #proposed_index = zeros((rows,1)) - 1
            while True:
                proposed_index = probsample_replace(arange(source_array_size), slots_to_be_sampled.size, p_array)
                try:
                    exclude_array = exclude_index[slots_to_be_sampled,]
                except:
                    exclude_array = None
                duplicate_indicator = find_duplicates_others(proposed_index, exclude_array)
                valid_index = slots_to_be_sampled[duplicate_indicator==0]
                sampled_choiceset_index[valid_index, j] = proposed_index[duplicate_indicator==0]
                if nonzerocounts(duplicate_indicator) == 0:
                    break

                slots_to_be_sampled = slots_to_be_sampled[duplicate_indicator>0]

            exclude_index = concatenate((exclude_index, take(sampled_choiceset_index,(j,), axis=1)), axis = 1)
    else:
        for j in range(columns):
            sampled_choiceset_index[:,j] = probsample_replace(arange(source_array_size), rows, p_array)

    if return_index:
        return sampled_choiceset_index
    else:
        return source_array[sampled_choiceset_index]

def sample_choice(prob_array, method="MC"):
    """sample chosen index given probability in prob_array
    prob_array - 2-d array for probability of being chosen, with probablities for a agent at
                 one row, and probabilities for alternatives at columns
    method - the method used to sample choice, either MC (Monte Carlo) or max_prob
    """

    if prob_array.ndim <> 2:
        raise RuntimeError, "prob_array must be a 2d array"

    rows, columns = prob_array.shape
    sum_prob_by_col = sum(prob_array, axis=1, dtype=float64)
    if not ma.allclose(sum_prob_by_col, ones((rows,))):
        strange_rows = where(sum_prob_by_col != ones((rows,)))
        raise RuntimeError, "prob_array must add up to 1 for each row. Abnormal rows: %s" % prob_array[strange_rows,:]

    if method.lower() == "mc":
        cum_prob = ncumsum(prob_array, axis=1)

        R = uniform(0,1,rows)
        R.resize((rows,1))

        match = (R < cum_prob)
        choices = argmax(match, axis=1)   # return the first index of 1 in each row
    elif method.lower() == "max_prob":
        choices = argmax(prob_array)

    if choices.size <> rows:
        raise RuntimeError, "having problems sample choice"

    return (arange(rows), choices)

def find_duplicates(source_array):
    """find duplicate values in a list or array
    return a numpy array of the same shape as source_array
    where nonzero indicates having duplicates, zero indicates none.
    """

    if not isinstance(source_array, ndarray):
        source_array = asarray(source_array)
    if source_array.ndim <> 1:
        source_array = source_array.ravel()
    array_size = source_array.size
    allsum = zeros(array_size, dtype="int32")
    allone = ones(array_size, dtype="int32")
    for element in source_array:
        allsum += equal(source_array, element)
    duplicate_indicator = allsum - allone

    #dup_idx = nonzero(duplicate_indicator)
    return duplicate_indicator

def find_duplicates_others(source_array, other_array):
    """find indices of duplicate values in src_array against other_array
    source_array - numpy array to be checked
    other_array - numpy array to be checked againt, must have compatiable shape[0]
                   with src_array
    """

    if other_array is None or len(other_array) == 0:
        return zeros(source_array.shape, dtype="int32")

    if other_array.shape[0] <> source_array.shape[0]:
        raise ValueError, "Arrays have incompatible shapes"
    source_array_rank = rank(source_array)


#! /usr/bin/env python
'''
Generates Inkscape SVG file containing box components needed to
laser cut a tabbed construction box taking kerf and clearance into account
based on Tabbed boxmaker by elliot white
Copyright (C) 2011 elliot white   elliot@twot.eu

Changes by Thore Mehr
Copyright (C) 2016 Thore Heiko Mehr thore.mehr@gmail.com
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 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
'''
__version__ = "1.0" ### please report bugs, suggestions etc to thore.mehr@gmail.com ###

import sys,inkex,simplestyle,gettext
_ = gettext.gettext

def drawS(XYstring,color):         # Draw lines from a list
  name='part'
  style = { 'stroke': color, 'fill': 'none' }
  drw = {'style':simplestyle.formatStyle(style),inkex.addNS('label','inkscape'):name,'d':XYstring}
  inkex.etree.SubElement(parent, inkex.addNS('path','svg'), drw )
  return

def groupdraw(XYstrings,colors)  :
  if len(XYstrings)==1:
    drawS(XYstrings[0],colors[0])
    return
  grp_name = 'Group'
  grp_attribs = {inkex.addNS('label','inkscape'):grp_name}
  grp = inkex.etree.SubElement(parent, 'g', grp_attribs)#the group to put everything in
  name='part'
  for i in range(len(XYstrings)):
    style = { 'stroke': colors[i], 'fill': 'none' }
    drw = {'style':simplestyle.formatStyle(style),inkex.addNS('label','inkscape'):name+str(i),'d':XYstrings[i]}
    inkex.etree.SubElement(grp, inkex.addNS('path','svg'), drw )
  return
def side((rx,ry),(sox,soy),(eox,eoy),tabVec,length,(dirx,diry),isTab):
  #       root startOffset endOffset tabVec length  direction  isTab

  divs=int(length/nomTab)  # divisions
  if not divs%2: divs-=1   # make divs odd
  divs=float(divs)
  tabs=(divs-1)/2          # tabs for side

  if equalTabs:
    gapWidth=tabWidth=length/divs
  else:
    tabWidth=nomTab
    gapWidth=(length-tabs*nomTab)/(divs-tabs)

  if isTab:                 # kerf correction
    gapWidth-=correction
    tabWidth+=correction
    first=correction/2
  else:
    gapWidth+=correction
    tabWidth-=correction
    first=-correction/2

  s=[]
  firstVec=0; secondVec=tabVec
  dirxN=0 if dirx else 1 # used to select operation on x or y
  diryN=0 if diry else 1
  (Vx,Vy)=(rx+sox*thickness,ry+soy*thickness)
  s='M '+str(Vx)+','+str(Vy)+' '

  if dirxN: Vy=ry # set correct line start
  if diryN: Vx=rx

  # generate line as tab or hole using:
  #   last co-ord:Vx,Vy ; tab dir:tabVec  ; direction:dirx,diry ; thickness:thickness
  #   divisions:divs ; gap width:gapWidth ; tab width:tabWidth

  for n in range(1,int(divs)):
    if n%2:
      Vx=Vx+dirx*gapWidth+dirxN*firstVec+first*dirx
      Vy=Vy+diry*gapWidth+diryN*firstVec+first*diry
      s+='L '+str(Vx)+','+str(Vy)+' '
      Vx=Vx+dirxN*secondVec
      Vy=Vy+diryN*secondVec
      s+='L '+str(Vx)+','+str(Vy)+' '
    else:
      Vx=Vx+dirx*tabWidth+dirxN*firstVec
      Vy=Vy+diry*tabWidth+diryN*firstVec
      s+='L '+str(Vx)+','+str(Vy)+' '
      Vx=Vx+dirxN*secondVec
      Vy=Vy+diryN*secondVec
      s+='L '+str(Vx)+','+str(Vy)+' '
    (secondVec,firstVec)=(-secondVec,-firstVec) # swap tab direction
    first=0
  s+='L '+str(rx+eox*thickness+dirx*length)+','+str(ry+eoy*thickness+diry*length)+' '
  return s

class OpenBoxMaker(inkex.Effect):
  def __init__(self):
      # Call the base class constructor.
      inkex.Effect.__init__(self)
      # Define options
      self.OptionParser.add_option('--boxtype',action='store',type='string',
        dest='boxtype',default='0',help='Boxtype')
      self.OptionParser.add_option('--unit',action='store',type='string',
        dest='unit',default='mm',help='Measure Units')
      self.OptionParser.add_option('--inside',action='store',type='int',
        dest='inside',default=0,help='Int/Ext Dimension')
      self.OptionParser.add_option('--length',action='store',type='float',
        dest='length',default=100,help='Length of Box')
      self.OptionParser.add_option('--width',action='store',type='float',
        dest='width',default=100,help='Width of Box')
      self.OptionParser.add_option('--depth',action='store',type='float',
        dest='height',default=100,help='Height of Box')
      self.OptionParser.add_option('--tab',action='store',type='float',
        dest='tab',default=25,help='Nominal Tab Width')
      self.OptionParser.add_option('--equal',action='store',type='int',
        dest='equal',default=0,help='Equal/Prop Tabs')
      self.OptionParser.add_option('--thickness',action='store',type='float',
        dest='thickness',default=10,help='Thickness of Material')
      self.OptionParser.add_option('--kerf',action='store',type='float',
        dest='kerf',default=0.5,help='Kerf (width) of cut')
      self.OptionParser.add_option('--clearance',action='store',type='float',
        dest='clearance',default=0.01,help='Clearance of joints')
      self.OptionParser.add_option('--style',action='store',type='int',
        dest='style',default=1,help='Layout/Style')
      self.OptionParser.add_option('--spacing',action='store',type='float',
        dest='spacing',default=25,help='Part Spacing')
      self.OptionParser.add_option('--hole',action='store',type='int',
        dest='hole',default=0,help='hole Style')
      self.OptionParser.add_option('--hole_heigth',action='store',type='float',
        dest='hole_height',default=10,help='hole_height')
      self.OptionParser.add_option('--hole_width',action='store',type='float',
        dest='hole_width',default=10,help='hole_width')

  def effect(self):
    global parent,nomTab,equalTabs,thickness,correction

        # Get access to main SVG document element and get its dimensions.
    svg = self.document.getroot()

        # Get the attibutes:
    widthDoc  = self.unittouu(svg.get('width'))
    heightDoc = self.unittouu(svg.get('height'))

        # Create a new layer.
    layer = inkex.etree.SubElement(svg, 'g')
    layer.set(inkex.addNS('label', 'inkscape'), 'newlayer')
    layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer')
    parent=self.current_layer

        # Get script's option values.
    boxtype=self.options.boxtype
    unit=self.options.unit
    inside=self.options.inside
    length = self.unittouu( str(self.options.length)  + unit )
    width = self.unittouu( str(self.options.width) + unit )
    height = self.unittouu( str(self.options.height)  + unit )
    thickness = self.unittouu( str(self.options.thickness)  + unit )
    nomTab = self.unittouu( str(self.options.tab) + unit )
    equalTabs=self.options.equal
    kerf = self.unittouu( str(self.options.kerf)  + unit )
    clearance = self.unittouu( str(self.options.clearance)  + unit )
    layout=self.options.style
    spacing = self.unittouu( str(self.options.spacing)  + unit )
    hole=self.options.hole
    hole_width=self.unittouu(str(self.options.hole_width)+unit)+2*kerf
    hole_height=self.unittouu(str(self.options.hole_height)+unit)+2*kerf
    if inside: # if inside dimension selected correct values to outside dimension
      length+=thickness*2
      width+=thickness*2
      height+=thickness# one time for open boxes
      #if boxtype=="2":
      height+=thickness #an other time for closed boxes
    correction=kerf-clearance
    text = inkex.etree.Element(inkex.addNS('text','svg'))
    text.text = "Outside:"+str(self.uutounit(length,unit))+"x"+str(self.uutounit(width,unit))+"x"+str(self.uutounit(height,unit))+"  "
    text.text += "Inside:"+str(self.uutounit(length-2*thickness,unit))+"x"+str(self.uutounit(width-2*thickness,unit))+"x"
    if boxtype=="1":
      text.text+=str(self.uutounit(height-2*thickness,unit))+"\n"
    else:
      text.text+=str(self.uutounit(height-thickness,unit))+"\n"
    layer.append(text)
    # check input values mainly to avoid python errors
    # TODO restrict values to *correct* solutions
    error=0

    if min(length,width,height)==0:
      inkex.errormsg(_('Error: Dimensions must be non zero'))
      error=1
    if max(length,width,height)>max(widthDoc,heightDoc)*10: # crude test
      inkex.errormsg(_('Error: Dimensions Too Large'))
      error=1
    if min(length,width,height)<3*nomTab:
      inkex.errormsg(_('Error: Tab size too large'))
      error=1
    if nomTab<thickness:
      inkex.errormsg(_('Error: Tab size too small'))
      error=1
    if thickness==0:
      inkex.errormsg(_('Error: Thickness is zero'))
      error=1
    if thickness>min(length,width,height)/3: # crude test
      inkex.errormsg(_('Error: Material too thick'))
      error=1
    if correction>min(length,width,height)/3: # crude test
      inkex.errormsg(_('Error: Kerf/Clearence too large'))
      error=1
    if spacing>max(length,width,height)*10: # crude test
      inkex.errormsg(_('Error: Spacing too large'))
      error=1
    if spacing<kerf:
      inkex.errormsg(_('Error: Spacing too small'))
      error=1

    if error: exit()

    # layout format:(rootx),(rooty),Xlength,Ylength,tabInfo,side to be flat, has hole
    # root= (spacing,length,width,height,thickness) * values in tuple
    # tabInfo= <abcd> 0=holes 1=tabs
    if   layout==1: # Diagramatic Layout
        pieces=[[(2,0,0,1,0),(2,0,0,1,0),length,width,0b0000,4,0],
        [(2,0,0,1,0),(3,0,1,1,0),length,height,0b1010,2,0],[(1,0,0,0,0),(2,0,0,1,0),height,width,0b1111,3,1],
        [(3,1,0,1,0),(2,0,0,1,0),height,width,0b1111,1,0],[(2,0,0,1,0),(1,0,0,0,0),length,height,0b1010,0,0],
        [(2,0,0,1,0),(3,0,1,2,0),length,width,0b0000,4,0]]
    elif layout==2: # Diagramatic Layout with Alternate Tab Arrangement
      pieces=[[(2,0,0,1,0),(2,0,0,1,0),length,width,0b1100,4,0],
      [(2,0,0,1,0),(3,0,1,1,0),length,height,0b1001,2,0],[(1,0,0,0,0),(2,0,0,1,0),height,width,0b1100,3,1],
      [(3,1,0,1,0),(2,0,0,1,0),height,width,0b0110,1,0],[(2,0,0,1,0),(1,0,0,0,0),length,height,0b1100,0,0],
      [(2,0,0,1,0),(3,0,1,2,0),length,width,0b1001,4,0]]
    elif layout==3: # Inline1 Layout
      pieces=[[(1,0,0,0,0),(1,0,0,0,0),length,width,0b0000,4,0],
      [(2,1,0,0,0),(1,0,0,0,0),height,width,0b0101,1,1],[(3,1,0,1,-1),(1,0,0,0,0),height,width,0b0101,1,0],
      [(1,0,0,0,0),(2,0,1,0,0),length,height,0b1111,2,0],[(1,0,0,0,0),(3,0,1,1,-1),length,height,0b1111,2,0],
      [(1,0,0,0,0),(4,0,1,2,0),length,width,0b0000,4,0]]
    #boxtype=1
    if boxtype=="1":
      pieces=pieces[0:5]
    for piece in pieces: # generate and draw each piece of the box
      (x_spaceing,x_length,x_width,x_height,x_thickness)=piece[0]
      (y_spaceing,y_length,y_width,y_height,y_thickness)=piece[1]
      if boxtype=="2":
        x_thickness=0
        y_thickness=0
      x=x_spaceing*spacing+x_length*length+x_width*width+x_height*height+x_thickness*thickness  # root x co-ord for piece
      y=y_spaceing*spacing+y_length*length+y_width*width+y_height*height+y_thickness*thickness  # root y co-ord for piece
      dx=piece[2]
      dy=piece[3]
      tabs=piece[4]
      a=tabs>>3&1;b=tabs>>2&1;c=tabs>>1&1;d=tabs&1; # extract tab status for each side
#      def side((rx,ry),(sox,soy),(eox,eoy),tabVec,length,(dirx,diry),isTab):
        #       root startOffset endOffset tabVec length  direction  isTab

      if piece[5]==0 and boxtype=="1":#top
#         s='M'+str(x+d*thickness)+','+str(y+a*thickness)+'l'+str(dx-2*b*thickness)+',0'
        s=side((x,y),(d,a),(-b,a),0,dx,(1,0),a)
      else:
        s=side((x,y),(d,a),(-b,a),-thickness if a else thickness,dx,(1,0),a)
      if piece[5]==1 and boxtype=="1":#right
#        s+='M'+str(x+dx-b*thickness)+','+str(y+a*thickness)+'l0,'+str(dy-2*c*thickness)
        s+=side((x+dx,y),(-b,a),(-b,-c),0,dy,(0,1),b)
      else:
        s+=side((x+dx,y),(-b,a),(-b,-c),thickness if b else -thickness,dy,(0,1),b)
      if piece[5]==2 and boxtype=="1":#bottom
#        s+='M'+str(x+dx-b*thickness)+','+str(y+dy-c*thickness)+'l'+str(-dx+2*b*thickness)+',0'
        s+=side((x+dx,y+dy),(-b,-c),(d,-c),0,dx,(-1,0),c)
      else:
        s+=side((x+dx,y+dy),(-b,-c),(d,-c),thickness if c else -thickness,dx,(-1,0),c)
      if piece[5]==3 and boxtype=="1":#left

#        s+='M'+str(x+d*thickness)+','+str(y+dy-c*thickness)+'l0,'+str(-dy+2*c*thickness)
        s+=side((x,y+dy),(d,-c),(d,a),0,dy,(0,-1),d)
      else:
        s+=side((x,y+dy),(d,-c),(d,a),-thickness if d else thickness,dy,(0,-1),d)

      s=[s]


# Copyright (c) 2015 Ultimaker B.V.
# Uranium is released under the terms of the LGPLv3 or higher.

import numpy
import numpy.linalg

##  Simple 2D-vector class based on numpy arrays.
#
#   This class represents a 2-dimensional vector.
class Vector2(object):
    Unit_X = None   # type: Vector2
    Unit_Y = None   # type: Vector2

    ##  Creates a new 2D vector.
    #
    #   Usage:
    #    - Vector2(x,y):          Creates a vector [x,y].
    #    - Vector2(data = [x,y]): Creates a vector [x,y].
    #    - Vector2():             Creates a vector [0,0].
    #
    #   \param data The numpy array of data to fill the vector with.
    def __init__(self, *args, **kwargs):
        if len(args) == 2: #X and Y specified.
            self._data = numpy.array([args[0], args[1]], dtype=numpy.float32)
        elif "data" in kwargs: #Data parameter specified.
            self._data = kwargs["data"].copy()
        else: #Nothing specified. Fill in 0.
            self._data = numpy.zeros(2, dtype=numpy.float32)

    ##  Computes the generalised cross product of this vector with another.
    #
    #   \param other The vector to compute the cross product with.
    #   \return The generalised cross product.
    def cross(self, other):
        return self._data[0] * other._data[1] - self._data[1] * other._data[0]

    ##  Computes the dot product of this vector and another.
    #
    #   \param other The vector to compute the dot product with.
    #   \return The dot product of the two vectors.
    def dot(self, other):
        return numpy.dot(self._data, other._data)

    ##  Gets the numpy array with the data.
    #
    #   \return A numpy array with the data of this vector.
    def getData(self):
        return self._data

    ##  Gets the Euclidean length of this vector.
    #
    #   \return The length of this vector.
    def length(self):
        return numpy.linalg.norm(self._data)

    ##  Gets a vector that is perpendicular to this vector.
    #
    #   There are exactly two vectors perpendicular. This method gets the
    #   perpendicular vector that is left of this vector.
    #
    #   \return A perpendicular vector.
    def perpendicular(self):
        return Vector2(-self._data[1], self._data[0])

    ##  Changes the x-component of this vector.
    #
    #   \param x The new x-component of the vector.
    def setX(self, x):
        self._data[0] = x

    ##  Changes the y-component of this vector.
    #
    #   \param y The new y-component of the vector.
    def setY(self, y):
        self._data[1] = y

    ##  Gets the x-component of the vector.
    #
    #   \return The x-component of the vector.
    @property
    def x(self):
        return self._data[0]

    ##  Gets the y-component of the vector.
    #
    #   \return The y-component of the vector.
    @property
    def y(self):
        return self._data[1]

    ##  Adds the specified vector to this vector element-wise.
    #
    #   \param other The vector that must be added to this vector.
    #   \return The result of the adding.
    def __add__(self, other):
        result = Vector2(data = self._data)
        result += other
        return result

    ##  Adds the specified vector in-place to this vector element-wise.
    #
    #   \param other The vector that must be added to this vector.
    def __iadd__(self, other):
        if type(other) is float:
            self._data[0] += other
            self._data[1] += other
        elif type(other) is Vector2:
            self._data[0] += other._data[0]
            self._data[1] += other._data[1]
        else:
            raise NotImplementedError()

        return self

    ##  Divides this vector by the specified vector element-wise.
    #
    #   \param other The vector by which this vector must be divided.
    #   \return The result of the division.
    def __truediv__(self, other):
        result = Vector2(data = self._data)
        result /= other
        return result

    ##  Divides this vector in-place by the specified vector element-wise.
    #
    #   \param other The vector by which this vector must be divided.
    def __itruediv__(self, other):
        if type(other) is float or type(other) is int:
            self._data /= other
            return self
        else:
            raise NotImplementedError()

    ##  Divides this vector by the specified vector element-wise.
    #
    #   \param other The vector by which this vector must be divided.
    #   \return The result of the division.
    def __rtruediv__(self, other):
        if type(other) is float:
            result = numpy.float32(other) / self._data
            return Vector2(result[0], result[1])
        else:
            raise NotImplementedError()

    ##  Multiplies the specified vector with this vector element-wise.
    #
    #   \param other The vector that must be multiplied with this vector.
    #   \return The result of the multiplication.
    def __mul__(self, other):
        result = Vector2(data = self._data)
        result *= other
        return result

    ##  Multiplies the specified vector in-place with this vector element-wise.
    #
    #   \param other The vector that must be multiplied with this vector.
    def __imul__(self, other):
        if type(other) is float:
            self._data[0] *= other
            self._data[1] *= other
        elif type(other) is Vector2:
            self._data[0] *= other._data[0]
            self._data[1] *= other._data[1]
        else:
            raise NotImplementedError()

        return self

    ##  Multiplies the specified vector with this vector element-wise.
    #
    #   \param other The vector that must be multiplied with this vector.
    def __rmul__(self, other):
        result = Vector2(data = self._data)
        result *= other
        return result

    ##  Negates the vector, resulting in a vector with the opposite direction.
    #
    #   \return The negated vector.
    def __neg__(self):
        return Vector2(data = -self._data)

    ##  Subtracts the specified vector from this vector element-wise.
    #
    #   \param other The vector that must be subtracted from this vector.
    #   \return The result of the subtraction.
    def __sub__(self, other):
        result = Vector2(data = self._data)
        result -= other
        return result

    ##  Subtracts the specified vector in-place from this vector element-wise.
    #
    #   \param other The vector that must be subtracted from this vector.
    def __isub__(self, other):
        if type(other) is float:
            self._data[0] -= other
            self._data[1] -= other
        elif type(other) is Vector2:
            self._data[0] -= other._data[0]
            self._data[1] -= other._data[1]
        else:
            raise NotImplementedError()
        return self

    ##  Gives a programmer-readable string representation of this vector.
    #
    #   The format is: [x,y]
    #
    #   \return A string representation of this vector.
    def __str__(self):


# -*- coding: utf-8 -*-
##############################################################################
#
#    OpenERP, Open Source Management Solution
#    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
#    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/>.
#
##############################################################################

from osv import fields, osv
import logging
import addons

class hr_employee_category(osv.osv):

    def name_get(self, cr, uid, ids, context=None):
        if not ids:
            return []
        reads = self.read(cr, uid, ids, ['name','parent_id'], context=context)
        res = []
        for record in reads:
            name = record['name']
            if record['parent_id']:
                name = record['parent_id'][1]+' / '+name
            res.append((record['id'], name))
        return res

    def _name_get_fnc(self, cr, uid, ids, prop, unknow_none, context=None):
        res = self.name_get(cr, uid, ids, context=context)
        return dict(res)

    _name = "hr.employee.category"
    _description = "Employee Category"
    _columns = {
        'name': fields.char("Category", size=64, required=True),
        'complete_name': fields.function(_name_get_fnc, type="char", string='Name'),
        'parent_id': fields.many2one('hr.employee.category', 'Parent Category', select=True),
        'child_ids': fields.one2many('hr.employee.category', 'parent_id', 'Child Categories'),
        'employee_ids': fields.many2many('hr.employee', 'employee_category_rel', 'category_id', 'emp_id', 'Employees'),
    }

    def _check_recursion(self, cr, uid, ids, context=None):
        level = 100
        while len(ids):
            cr.execute('select distinct parent_id from hr_employee_category where id IN %s', (tuple(ids), ))
            ids = filter(None, map(lambda x:x[0], cr.fetchall()))
            if not level:
                return False
            level -= 1
        return True

    _constraints = [
        (_check_recursion, 'Error ! You cannot create recursive Categories.', ['parent_id'])
    ]

hr_employee_category()

class hr_job(osv.osv):

    def _no_of_employee(self, cr, uid, ids, name, args, context=None):
        res = {}
        for job in self.browse(cr, uid, ids, context=context):
            nb_employees = len(job.employee_ids or [])
            res[job.id] = {
                'no_of_employee': nb_employees,
                'expected_employees': nb_employees + job.no_of_recruitment,
            }
        return res

    def _get_job_position(self, cr, uid, ids, context=None):
        res = []
        for employee in self.pool.get('hr.employee').browse(cr, uid, ids, context=context):
            if employee.job_id:
                res.append(employee.job_id.id)
        return res

    _name = "hr.job"
    _description = "Job Description"
    _columns = {
        'name': fields.char('Job Name', size=128, required=True, select=True),
        'expected_employees': fields.function(_no_of_employee, string='Expected Employees', help='Required number of employees in total for that job.',
            store = {
                'hr.job': (lambda self,cr,uid,ids,c=None: ids, ['no_of_recruitment'], 10),
                'hr.employee': (_get_job_position, ['job_id'], 10),
            },
            multi='no_of_employee'),
        'no_of_employee': fields.function(_no_of_employee, string="Number of Employees", help='Number of employees with that job.',
            store = {
                'hr.employee': (_get_job_position, ['job_id'], 10),
            },
            multi='no_of_employee'),
        'no_of_recruitment': fields.float('Expected in Recruitment'),
        'employee_ids': fields.one2many('hr.employee', 'job_id', 'Employees'),
        'description': fields.text('Job Description'),
        'requirements': fields.text('Requirements'),
        'department_id': fields.many2one('hr.department', 'Department'),
        'company_id': fields.many2one('res.company', 'Company'),
        'state': fields.selection([('open', 'In Position'),('old', 'Old'),('recruit', 'In Recruitement')], 'State', readonly=True, required=True),
    }
    _defaults = {
        'expected_employees': 1,
        'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'hr.job', context=c),
        'state': 'open',
    }

    _sql_constraints = [
        ('name_company_uniq', 'unique(name, company_id)', 'The name of the job position must be unique per company!'),
    ]

    def on_change_expected_employee(self, cr, uid, ids, no_of_recruitment, no_of_employee, context=None):
        if context is None:
            context = {}
        return {'value': {'expected_employees': no_of_recruitment + no_of_employee}}

    def job_old(self, cr, uid, ids, *args):
        self.write(cr, uid, ids, {'state': 'old', 'no_of_recruitment': 0})
        return True

    def job_recruitement(self, cr, uid, ids, *args):
        for job in self.browse(cr, uid, ids):
            no_of_recruitment = job.no_of_recruitment == 0 and 1 or job.no_of_recruitment
            self.write(cr, uid, [job.id], {'state': 'recruit', 'no_of_recruitment': no_of_recruitment})
        return True

    def job_open(self, cr, uid, ids, *args):
        self.write(cr, uid, ids, {'state': 'open', 'no_of_recruitment': 0})
        return True

hr_job()

class hr_employee(osv.osv):
    _name = "hr.employee"
    _description = "Employee"
    _inherits = {'resource.resource': "resource_id"}
    _columns = {
        'country_id': fields.many2one('res.country', 'Nationality'),
        'birthday': fields.date("Date of Birth"),
        'ssnid': fields.char('SSN No', size=32, help='Social Security Number'),
        'sinid': fields.char('SIN No', size=32, help="Social Insurance Number"),
        'identification_id': fields.char('Identification No', size=32),
        'otherid': fields.char('Other Id', size=64),
        'gender': fields.selection([('male', 'Male'),('female', 'Female')], 'Gender'),
        'marital': fields.selection([('single', 'Single'), ('married', 'Married'), ('widower', 'Widower'), ('divorced', 'Divorced')], 'Marital Status'),
        'department_id':fields.many2one('hr.department', 'Department'),
        'address_id': fields.many2one('res.partner.address', 'Working Address'),
        'address_home_id': fields.many2one('res.partner.address', 'Home Address'),
        'partner_id': fields.related('address_home_id', 'partner_id', type='many2one', relation='res.partner', readonly=True, help="Partner that is related to the current employee. Accounting transaction will be written on this partner belongs to employee."),
        'bank_account_id':fields.many2one('res.partner.bank', 'Bank Account Number', domain="[('partner_id','=',partner_id)]", help="Employee bank salary account"),
        'work_phone': fields.char('Work Phone', size=32, readonly=False),
        'mobile_phone': fields.char('Work Mobile', size=32, readonly=False),
        'work_email': fields.char('Work E-mail', size=240),
        'work_location': fields.char('Office Location', size=32),
        'notes': fields.text('Notes'),
        'parent_id': fields.many2one('hr.employee', 'Manager'),
        'category_ids': fields.many2many('hr.employee.category', 'employee_category_rel', 'emp_id', 'category_id', 'Categories'),
        'child_ids': fields.one2many('hr.employee', 'parent_id', 'Subordinates'),
        'resource_id': fields.many2one('resource.resource', 'Resource', ondelete='cascade', required=True),
        'coach_id': fields.many2one('hr.employee', 'Coach'),
        'job_id': fields.many2one('hr.job', 'Job'),
        'photo': fields.binary('Photo'),
        'passport_id':fields.char('Passport No', size=64),
        'color': fields.integer('Color Index'),
        'city': fields.related('address_id', 'city', type='char', string='City'),
        'login': fields.related('user_id', 'login', type='char', string='Login', readonly=1),
    }

    def unlink(self, cr, uid, ids, context=None):
        resource_obj = self.pool.get('resource.resource')
        resource_ids = []
        for employee in self.browse(cr, uid, ids, context=context):
            resource = employee.resource_id
            if resource:
                resource_ids.append(resource.id)
        if resource_ids:
            resource_obj.unlink(cr, uid, resource_ids, context=context)
        return super(hr_employee, self).unlink(cr, uid, ids, context=context)

    def onchange_address_id(self, cr, uid, ids, address, context=None):
        if address:
            address = self.pool.get('res.partner.address').browse(cr, uid, address, context=context)
            return {'value': {'work_email': address.email, 'work_phone': address.phone, 'mobile_phone': address.mobile}}
        return {'value': {}}

    def onchange_company(self, cr, uid, ids, company, context=None):
        address_id = False
        if company:
            company_id = self.pool.get('res.company').browse(cr, uid, company, context=context)
            address = self.pool.get('res.partner').address_get(cr, uid, [company_id.partner_id.id], ['default'])
            address_id = address and address['default'] or False


#! /usr/bin/env python
# encoding: utf-8
# WARNING! Do not edit! https://waf.io/book/index.html#_obtaining_the_waf_file

import os,re,sys
from waflib import Utils,Logs,Errors
NOT_RUN=0
MISSING=1
CRASHED=2
EXCEPTION=3
SKIPPED=8
SUCCESS=9
ASK_LATER=-1
SKIP_ME=-2
RUN_ME=-3
COMPILE_TEMPLATE_SHELL='''
def f(tsk):
	env = tsk.env
	gen = tsk.generator
	bld = gen.bld
	wd = getattr(tsk, 'cwd', None)
	p = env.get_flat
	tsk.last_cmd = cmd = \'\'\' %s \'\'\' % s
	return tsk.exec_command(cmd, cwd=wd, env=env.env or None)
'''
COMPILE_TEMPLATE_NOSHELL='''
def f(tsk):
	env = tsk.env
	gen = tsk.generator
	bld = gen.bld
	wd = getattr(tsk, 'cwd', None)
	def to_list(xx):
		if isinstance(xx, str): return [xx]
		return xx
	tsk.last_cmd = lst = []
	%s
	lst = [x for x in lst if x]
	return tsk.exec_command(lst, cwd=wd, env=env.env or None)
'''
classes={}
class store_task_type(type):
	def __init__(cls,name,bases,dict):
		super(store_task_type,cls).__init__(name,bases,dict)
		name=cls.__name__
		if name.endswith('_task'):
			name=name.replace('_task','')
		if name!='evil'and name!='TaskBase':
			global classes
			if getattr(cls,'run_str',None):
				(f,dvars)=compile_fun(cls.run_str,cls.shell)
				cls.hcode=cls.run_str
				cls.orig_run_str=cls.run_str
				cls.run_str=None
				cls.run=f
				cls.vars=list(set(cls.vars+dvars))
				cls.vars.sort()
			elif getattr(cls,'run',None)and not'hcode'in cls.__dict__:
				cls.hcode=Utils.h_fun(cls.run)
			if sys.hexversion>0x3000000:
				cls.hcode=cls.hcode.encode('iso8859-1','xmlcharrefreplace')
			getattr(cls,'register',classes)[name]=cls
evil=store_task_type('evil',(object,),{})
class TaskBase(evil):
	color='GREEN'
	ext_in=[]
	ext_out=[]
	before=[]
	after=[]
	hcode=''
	def __init__(self,*k,**kw):
		self.hasrun=NOT_RUN
		try:
			self.generator=kw['generator']
		except KeyError:
			self.generator=self
	def __repr__(self):
		return'\n\t{task %r: %s %s}'%(self.__class__.__name__,id(self),str(getattr(self,'fun','')))
	def __str__(self):
		if hasattr(self,'fun'):
			return self.fun.__name__
		return self.__class__.__name__
	def __hash__(self):
		return id(self)
	def keyword(self):
		if hasattr(self,'fun'):
			return'Function'
		return'Processing'
	def exec_command(self,cmd,**kw):
		bld=self.generator.bld
		try:
			if not kw.get('cwd',None):
				kw['cwd']=bld.cwd
		except AttributeError:
			bld.cwd=kw['cwd']=bld.variant_dir
		return bld.exec_command(cmd,**kw)
	def runnable_status(self):
		return RUN_ME
	def process(self):
		m=self.master
		if m.stop:
			m.out.put(self)
			return
		try:
			del self.generator.bld.task_sigs[self.uid()]
		except KeyError:
			pass
		try:
			self.generator.bld.returned_tasks.append(self)
			self.log_display(self.generator.bld)
			ret=self.run()
		except Exception:
			self.err_msg=Utils.ex_stack()
			self.hasrun=EXCEPTION
			m.error_handler(self)
			m.out.put(self)
			return
		if ret:
			self.err_code=ret
			self.hasrun=CRASHED
		else:
			try:
				self.post_run()
			except Errors.WafError:
				pass
			except Exception:
				self.err_msg=Utils.ex_stack()
				self.hasrun=EXCEPTION
			else:
				self.hasrun=SUCCESS
		if self.hasrun!=SUCCESS:
			m.error_handler(self)
		m.out.put(self)
	def run(self):
		if hasattr(self,'fun'):
			return self.fun(self)
		return 0
	def post_run(self):
		pass
	def log_display(self,bld):
		if self.generator.bld.progress_bar==3:
			return
		s=self.display()
		if s:
			if bld.logger:
				logger=bld.logger
			else:
				logger=Logs
			if self.generator.bld.progress_bar==1:
				c1=Logs.colors.cursor_off
				c2=Logs.colors.cursor_on
				logger.info(s,extra={'stream':sys.stderr,'terminator':'','c1':c1,'c2':c2})
			else:
				logger.info(s,extra={'terminator':'','c1':'','c2':''})
	def display(self):
		col1=Logs.colors(self.color)
		col2=Logs.colors.NORMAL
		master=self.master
		def cur():
			tmp=-1
			if hasattr(master,'ready'):
				tmp-=master.ready.qsize()
			return master.processed+tmp
		if self.generator.bld.progress_bar==1:
			return self.generator.bld.progress_line(cur(),master.total,col1,col2)
		if self.generator.bld.progress_bar==2:
			ela=str(self.generator.bld.timer)
			try:
				ins=','.join([n.name for n in self.inputs])
			except AttributeError:
				ins=''
			try:
				outs=','.join([n.name for n in self.outputs])
			except AttributeError:
				outs=''
			return'|Total %s|Current %s|Inputs %s|Outputs %s|Time %s|\n'%(master.total,cur(),ins,outs,ela)
		s=str(self)
		if not s:
			return None
		total=master.total
		n=len(str(total))
		fs='[%%%dd/%%%dd] %%s%%s%%s%%s\n'%(n,n)
		kw=self.keyword()
		if kw:
			kw+=' '
		return fs%(cur(),total,kw,col1,s,col2)
	def attr(self,att,default=None):
		ret=getattr(self,att,self)
		if ret is self:return getattr(self.__class__,att,default)
		return ret
	def hash_constraints(self):
		cls=self.__class__
		tup=(str(cls.before),str(cls.after),str(cls.ext_in),str(cls.ext_out),cls.__name__,cls.hcode)
		h=hash(tup)
		return h
	def format_error(self):
		msg=getattr(self,'last_cmd','')
		name=getattr(self.generator,'name','')
		if getattr(self,"err_msg",None):
			return self.err_msg
		elif not self.hasrun:
			return'task in %r was not executed for some reason: %r'%(name,self)
		elif self.hasrun==CRASHED:
			try:
				return' -> task in %r failed (exit status %r): %r\n%r'%(name,self.err_code,self,msg)
			except AttributeError:
				return' -> task in %r failed: %r\n%r'%(name,self,msg)
		elif self.hasrun==MISSING:
			return' -> missing files in %r: %r\n%r'%(name,self,msg)
		else:
			return'invalid status for task in %r: %r'%(name,self.hasrun)
	def colon(self,var1,var2):
		tmp=self.env[var1]
		if not tmp:
			return[]
		if isinstance(var2,str):
			it=self.env[var2]
		else:
			it=var2
		if isinstance(tmp,str):
			return[tmp%x for x in it]
		else:
			lst=[]
			for y in it:
				lst.extend(tmp)
				lst.append(y)
			return lst
class Task(TaskBase):
	vars=[]
	shell=False
	def __init__(self,*k,**kw):
		TaskBase.__init__(self,*k,**kw)
		self.env=kw['env']
		self.inputs=[]
		self.outputs=[]
		self.dep_nodes=[]
		self.run_after=set([])
	def __str__(self):
		name=self.__class__.__name__
		if self.outputs:
			if(name.endswith('lib')or name.endswith('program'))or not self.inputs:
				node=self.outputs[0]
				return node.path_from(node.ctx.launch_node())
		if not(self.inputs or self.outputs):
			return self.__class__.__name__
		if len(self.inputs)==1:
			node=self.inputs[0]
			return node.path_from(node.ctx.launch_node())
		src_str=' '.join([a.path_from(a.ctx.launch_node())for a in self.inputs])
		tgt_str=' '.join([a.path_from(a.ctx.launch_node())for a in self.outputs])
		if self.outputs:sep=' -> '
		else:sep=''
		return'%s: %s%s%s'%(self.__class__.__name__.replace('_task',''),src_str,sep,tgt_str)
	def keyword(self):
		name=self.__class__.__name__
		if name.endswith('lib')or name.endswith('program'):
			return'Linking'
		if len(self.inputs)==1 and len(self.outputs)==1:
			return'Compiling'
		if not self.inputs:
			if self.outputs:
				return'Creating'
			else:
				return'Running'
		return'Processing'
	def __repr__(self):
		try:
			ins=",".join([x.name for x in self.inputs])
			outs=",".join([x.name for x in self.outputs])
		except AttributeError:
			ins=",".join([str(x)for x in self.inputs])
			outs=",".join([str(x)for x in self.outputs])
		return"".join(['\n\t{task %r: '%id(self),self.__class__.__name__," ",ins," -> ",outs,'}'])
	def uid(self):
		try:
			return self.uid_
		except AttributeError:
			m=Utils.md5()
			up=m.update
			up(self.__class__.__name__)
			for x in self.inputs+self.outputs:
				up(x.abspath())
			self.uid_=m.digest()
			return self.uid_
	def set_inputs(self,inp):
		if isinstance(inp,list):self.inputs+=inp
		else:self.inputs.append(inp)
	def set_outputs(self,out):
		if isinstance(out,list):self.outputs+=out
		else:self.outputs.append(out)
	def set_run_after(self,task):
		assert isinstance(task,TaskBase)
		self.run_after.add(task)
	def signature(self):
		try:return self.cache_sig
		except AttributeError:pass
		self.m=Utils.md5()
		self.m.update(self.hcode)
		self.sig_explicit_deps()
		self.sig_vars()
		if self.scan:
			try:
				self.sig_implicit_deps()
			except Errors.TaskRescan:
				return self.signature()
		ret=self.cache_sig=self.m.digest()
		return ret
	def runnable_status(self):
		for t in self.run_after:
			if not t.hasrun:
				return ASK_LATER
		bld=self.generator.bld
		try:
			new_sig=self.signature()
		except Errors.TaskNotReady:
			return ASK_LATER
		key=self.uid()
		try:
			prev_sig=bld.task_sigs[key]
		except KeyError:
			Logs.debug("task: task %r must run as it was never run before or the task code changed"%self)
			return RUN_ME
		for node in self.outputs:
			try:
				if node.sig!=new_sig:
					return RUN_ME
			except AttributeError:
				Logs.debug("task: task %r must run as the output nodes do not exist"%self)
				return RUN_ME
		if new_sig!=prev_sig:
			return RUN_ME
		return SKIP_ME
	def post_run(self):
		bld=self.generator.bld
		sig=self.signature()
		for node in self.outputs:
			try:
				os.stat(node.abspath())
			except OSError:
				self.hasrun=MISSING
				self.err_msg='-> missing file: %r'%node.abspath()
				raise Errors.WafError(self.err_msg)
			node.sig=node.cache_sig=sig
		bld.task_sigs[self.uid()]=self.cache_sig
	def sig_explicit_deps(self):
		bld=self.generator.bld
		upd=self.m.update
		for x in self.inputs+self.dep_nodes:
			try:
				upd(x.get_bld_sig())
			except(AttributeError,TypeError):
				raise Errors.WafError('Missing node signature for %r (required by %r)'%(x,self))
		if bld.deps_man:
			additional_deps=bld.deps_man
			for x in self.inputs+self.outputs:
				try:
					d=additional_deps[id(x)]
				except KeyError:
					continue
				for v in d:
					if isinstance(v,bld.root.__class__):
						try:
							v=v.get_bld_sig()
						except AttributeError:
							raise Errors.WafError('Missing node signature for %r (required by %r)'%(v,self))
					elif hasattr(v,'__call__'):
						v=v()
					upd(v)
		return self.m.digest()
	def sig_vars(self):
		bld=self.generator.bld
		env=self.env
		upd=self.m.update
		act_sig=bld.hash_env_vars(env,self.__class__.vars)
		upd(act_sig)
		dep_vars=getattr(self,'dep_vars',None)
		if dep_vars:
			upd(bld.hash_env_vars(env,dep_vars))
		return self.m.digest()
	scan=None
	def sig_implicit_deps(self):
		bld=self.generator.bld
		key=self.uid()
		prev=bld.task_sigs.get((key,'imp'),[])
		if prev:
			try:
				if prev==self.compute_sig_implicit_deps():
					return prev
			except Errors.TaskNotReady:
				raise
			except EnvironmentError:
				for x in bld.node_deps.get(self.uid(),[]):
					if not x.is_bld():
						try:
							os.stat(x.abspath())
						except OSError:
							try:
								del x.parent.children[x.name]
							except KeyError:
								pass
			del bld.task_sigs[(key,'imp')]
			raise Errors.TaskRescan('rescan')
		(nodes,names)=self.scan()
		if Logs.verbose:
			Logs.debug('deps: scanner for %s returned %s %s'%(str(self),str(nodes),str(names)))
		bld.node_deps[key]=nodes
		bld.raw_deps[key]=names
		self.are_implicit_nodes_ready()
		try:
			bld.task_sigs[(key,'imp')]=sig=self.compute_sig_implicit_deps()
		except Exception:
			if Logs.verbose:
				for k in bld.node_deps.get(self.uid(),[]):
					try:
						k.get_bld_sig()
					except Exception:
						Logs.warn('Missing signature for node %r (may cause rebuilds)'%k)
		else:
			return sig
	def compute_sig_implicit_deps(self):
		upd=self.m.update
		bld=self.generator.bld
		self.are_implicit_nodes_ready()
		for k in bld.node_deps.get(self.uid(),[]):
			upd(k.get_bld_sig())
		return self.m.digest()
	def are_implicit_nodes_ready(self):
		bld=self.generator.bld
		try:
			cache=bld.dct_implicit_nodes
		except AttributeError:
			bld.dct_implicit_nodes=cache={}
		try:
			dct=cache[bld.cur]
		except KeyError:
			dct=cache[bld.cur]={}
			for tsk in bld.cur_tasks:
				for x in tsk.outputs:
					dct[x]=tsk
		modified=False
		for x in bld.node_deps.get(self.uid(),[]):
			if x in dct:
				self.run_after.add(dct[x])
				modified=True
		if modified:
			for tsk in self.run_after:
				if not tsk.hasrun:
					raise Errors.TaskNotReady('not ready')
if sys.hexversion>0x3000000:
	def uid(self):
		try:
			return self.uid_
		except AttributeError:
			m=Utils.md5()
			up=m.update
			up(self.__class__.__name__.encode('iso8859-1','xmlcharrefreplace'))
			for x in self.inputs+self.outputs:
				up(x.abspath().encode('iso8859-1','xmlcharrefreplace'))
			self.uid_=m.digest()
			return self.uid_
	uid.__doc__=Task.uid.__doc__
	Task.uid=uid
def is_before(t1,t2):
	to_list=Utils.to_list
	for k in to_list(t2.ext_in):
		if k in to_list(t1.ext_out):
			return 1
	if t1.__class__.__name__ in to_list(t2.after):
		return 1
	if t2.__class__.__name__ in to_list(t1.before):
		return 1
	return 0
def set_file_constraints(tasks):
	ins=Utils.defaultdict(set)
	outs=Utils.defaultdict(set)
	for x in tasks:
		for a in getattr(x,'inputs',[])+getattr(x,'dep_nodes',[]):
			ins[id(a)].add(x)
		for a in getattr(x,'outputs',[]):
			outs[id(a)].add(x)
	links=set(ins.keys()).intersection(outs.keys())
	for k in links:


# -*- coding: utf-'8' "-*-"

import logging

from openerp.osv import osv, fields
from openerp.tools import float_round, float_repr
from openerp.tools.translate import _

_logger = logging.getLogger(__name__)

def _partner_format_address(address1=False, address2=False):
    return ' '.join((address1 or '', address2 or '')).strip()

def _partner_split_name(partner_name):
    return [' '.join(partner_name.split()[-1:]), ' '.join(partner_name.split()[:-1])]

class ValidationError(ValueError):
    """ Used for value error when validating transaction data coming from acquirers. """
    pass

class PaymentAcquirer(osv.Model):
    """ Acquirer Model. Each specific acquirer can extend the model by adding
    its own fields, using the acquirer_name as a prefix for the new fields.
    Using the required_if_provider='<name>' attribute on fields it is possible
    to have required fields that depend on a specific acquirer.

    Each acquirer has a link to an ir.ui.view record that is a template of
    a button used to display the payment form. See examples in ``payment_ogone``
    and ``payment_paypal`` modules.

    Methods that should be added in an acquirer-specific implementation:

     - ``<name>_form_generate_values(self, cr, uid, id, reference, amount, currency,
       partner_id=False, partner_values=None, tx_custom_values=None, context=None)``:
       method that generates the values used to render the form button template.
     - ``<name>_get_form_action_url(self, cr, uid, id, context=None):``: method
       that returns the url of the button form. It is used for example in
       ecommerce application, if you want to post some data to the acquirer.
     - ``<name>_compute_fees(self, cr, uid, id, amount, currency_id, country_id,
       context=None)``: computed the fees of the acquirer, using generic fields
       defined on the acquirer model (see fields definition).

    Each acquirer should also define controllers to handle communication between
    OpenERP and the acquirer. It generally consists in return urls given to the
    button form and that the acquirer uses to send the customer back after the
    transaction, with transaction details given as a POST request.
    """
    _name = 'payment.acquirer'
    _description = 'Payment Acquirer'
    _order = 'sequence'

    def _get_providers(self, cr, uid, context=None):
        return []

    # indirection to ease inheritance
    _provider_selection = lambda self, *args, **kwargs: self._get_providers(*args, **kwargs)

    _columns = {
        'name': fields.char('Name', required=True),
        'provider': fields.selection(_provider_selection, string='Provider', required=True),
        'company_id': fields.many2one('res.company', 'Company', required=True),
        'pre_msg': fields.html('Message', translate=True,
            help='Message displayed to explain and help the payment process.'),
        'post_msg': fields.html('Thanks Message', help='Message displayed after having done the payment process.'),
        'validation': fields.selection(
            [('manual', 'Manual'), ('automatic', 'Automatic')],
            string='Process Method',
            help='Static payments are payments like transfer, that require manual steps.'),
        'view_template_id': fields.many2one('ir.ui.view', 'Form Button Template', required=True),
        'environment': fields.selection(
            [('test', 'Test'), ('prod', 'Production')],
            string='Environment', oldname='env'),
        'website_published': fields.boolean(
            'Visible in Portal / Website', copy=False,
            help="Make this payment acquirer available (Customer invoices, etc.)"),
        'auto_confirm': fields.selection(
            [('none', 'No automatic confirmation'),
             ('at_pay_confirm', 'At payment confirmation'),
             ('at_pay_now', 'At payment')],
            string='Order Confirmation', required=True),
        # Fees
        'fees_active': fields.boolean('Compute fees'),
        'fees_dom_fixed': fields.float('Fixed domestic fees'),
        'fees_dom_var': fields.float('Variable domestic fees (in percents)'),
        'fees_int_fixed': fields.float('Fixed international fees'),
        'fees_int_var': fields.float('Variable international fees (in percents)'),
        'sequence': fields.integer('Sequence', help="Determine the display order"),
    }

    _defaults = {
        'company_id': lambda self, cr, uid, obj, ctx=None: self.pool['res.users'].browse(cr, uid, uid).company_id.id,
        'environment': 'test',
        'validation': 'automatic',
        'website_published': True,
        'auto_confirm': 'at_pay_confirm',
    }

    def _check_required_if_provider(self, cr, uid, ids, context=None):
        """ If the field has 'required_if_provider="<provider>"' attribute, then it
        required if record.provider is <provider>. """
        for acquirer in self.browse(cr, uid, ids, context=context):
            if any(getattr(f, 'required_if_provider', None) == acquirer.provider and not acquirer[k] for k, f in self._fields.items()):
                return False
        return True

    _constraints = [
        (_check_required_if_provider, 'Required fields not filled', ['required for this provider']),
    ]

    def get_form_action_url(self, cr, uid, id, context=None):
        """ Returns the form action URL, for form-based acquirer implementations. """
        acquirer = self.browse(cr, uid, id, context=context)
        if hasattr(self, '%s_get_form_action_url' % acquirer.provider):
            return getattr(self, '%s_get_form_action_url' % acquirer.provider)(cr, uid, id, context=context)
        return False

    def form_preprocess_values(self, cr, uid, id, reference, amount, currency_id, tx_id, partner_id, partner_values, tx_values, context=None):
        """  Pre process values before giving them to the acquirer-specific render
        methods. Those methods will receive:

             - partner_values: will contain name, lang, email, zip, address, city,
               country_id (int or False), country (browse or False), phone, reference
             - tx_values: will contain reference, amount, currency_id (int or False),
               currency (browse or False), partner (browse or False)
        """
        acquirer = self.browse(cr, uid, id, context=context)

        if tx_id:
            tx = self.pool.get('payment.transaction').browse(cr, uid, tx_id, context=context)
            tx_data = {
                'reference': tx.reference,
                'amount': tx.amount,
                'currency_id': tx.currency_id.id,
                'currency': tx.currency_id,
                'partner': tx.partner_id,
            }
            partner_data = {
                'name': tx.partner_name,
                'lang': tx.partner_lang,
                'email': tx.partner_email,
                'zip': tx.partner_zip,
                'address': tx.partner_address,
                'city': tx.partner_city,
                'country_id': tx.partner_country_id.id,
                'country': tx.partner_country_id,
                'phone': tx.partner_phone,
                'reference': tx.partner_reference,
                'state': None,
            }
        else:
            if partner_id:
                partner = self.pool['res.partner'].browse(cr, uid, partner_id, context=context)
                partner_data = {
                    'name': partner.name,
                    'lang': partner.lang,
                    'email': partner.email,
                    'zip': partner.zip,
                    'city': partner.city,
                    'address': _partner_format_address(partner.street, partner.street2),
                    'country_id': partner.country_id.id,
                    'country': partner.country_id,
                    'phone': partner.phone,
                    'state': partner.state_id,
                }
            else:
                partner, partner_data = False, {}
            partner_data.update(partner_values)

            if currency_id:
                currency = self.pool['res.currency'].browse(cr, uid, currency_id, context=context)
            else:
                currency = self.pool['res.users'].browse(cr, uid, uid, context=context).company_id.currency_id
            tx_data = {
                'reference': reference,
                'amount': amount,
                'currency_id': currency.id,
                'currency': currency,
                'partner': partner,
            }

        # update tx values
        tx_data.update(tx_values)

        # update partner values
        if not partner_data.get('address'):
            partner_data['address'] = _partner_format_address(partner_data.get('street', ''), partner_data.get('street2', ''))
        if not partner_data.get('country') and partner_data.get('country_id'):
            partner_data['country'] = self.pool['res.country'].browse(cr, uid, partner_data.get('country_id'), context=context)
        partner_data.update({
            'first_name': _partner_split_name(partner_data['name'])[0],
            'last_name': _partner_split_name(partner_data['name'])[1],
        })

        # compute fees
        fees_method_name = '%s_compute_fees' % acquirer.provider
        if hasattr(self, fees_method_name):
            fees = getattr(self, fees_method_name)(
                cr, uid, id, tx_data['amount'], tx_data['currency_id'], partner_data['country_id'], context=None)
            tx_data['fees'] = float_round(fees, 2)

        return (partner_data, tx_data)

    def render(self, cr, uid, id, reference, amount, currency_id, tx_id=None, partner_id=False, partner_values=None, tx_values=None, context=None):
        """ Renders the form template of the given acquirer as a qWeb template.
        All templates will receive:

         - acquirer: the payment.acquirer browse record
         - user: the current user browse record
         - currency_id: id of the transaction currency
         - amount: amount of the transaction
         - reference: reference of the transaction
         - partner: the current partner browse record, if any (not necessarily set)
         - partner_values: a dictionary of partner-related values
         - tx_values: a dictionary of transaction related values that depends on
                      the acquirer. Some specific keys should be managed in each
                      provider, depending on the features it offers:

          - 'feedback_url': feedback URL, controler that manage answer of the acquirer
                            (without base url) -> FIXME
          - 'return_url': URL for coming back after payment validation (wihout
                          base url) -> FIXME
          - 'cancel_url': URL if the client cancels the payment -> FIXME
          - 'error_url': URL if there is an issue with the payment -> FIXME

         - context: OpenERP context dictionary

        :param string reference: the transaction reference
        :param float amount: the amount the buyer has to pay
        :param res.currency browse record currency: currency
        :param int tx_id: id of a transaction; if set, bypasses all other given
                          values and only render the already-stored transaction
        :param res.partner browse record partner_id: the buyer
        :param dict partner_values: a dictionary of values for the buyer (see above)
        :param dict tx_custom_values: a dictionary of values for the transction
                                      that is given to the acquirer-specific method
                                      generating the form values
        :param dict context: OpenERP context
        """
        if context is None:
            context = {}
        if tx_values is None:
            tx_values = {}
        if partner_values is None:
            partner_values = {}
        acquirer = self.browse(cr, uid, id, context=context)

        # pre-process values
        amount = float_round(amount, 2)
        partner_values, tx_values = self.form_preprocess_values(
            cr, uid, id, reference, amount, currency_id, tx_id, partner_id,
            partner_values, tx_values, context=context)

        # call <name>_form_generate_values to update the tx dict with acqurier specific values
        cust_method_name = '%s_form_generate_values' % (acquirer.provider)
        if hasattr(self, cust_method_name):
            method = getattr(self, cust_method_name)
            partner_values, tx_values = method(cr, uid, id, partner_values, tx_values, context=context)

        qweb_context = {
            'tx_url': context.get('tx_url', self.get_form_action_url(cr, uid, id, context=context)),
            'submit_class': context.get('submit_class', 'btn btn-link'),
            'submit_txt': context.get('submit_txt'),
            'acquirer': acquirer,
            'user': self.pool.get("res.users").browse(cr, uid, uid, context=context),
            'reference': tx_values['reference'],
            'amount': tx_values['amount'],
            'currency': tx_values['currency'],
            'partner': tx_values.get('partner'),
            'partner_values': partner_values,
            'tx_values': tx_values,
            'context': context,
        }

        # because render accepts view ids but not qweb -> need to use the xml_id
        return self.pool['ir.ui.view'].render(cr, uid, acquirer.view_template_id.xml_id, qweb_context, engine='ir.qweb', context=context)

    def _wrap_payment_block(self, cr, uid, html_block, amount, currency_id, context=None):
        payment_header = _('Pay safely online')
        amount_str = float_repr(amount, self.pool.get('decimal.precision').precision_get(cr, uid, 'Account'))
        currency = self.pool['res.currency'].browse(cr, uid, currency_id, context=context)
        currency_str = currency.symbol or currency.name
        amount = u"%s %s" % ((currency_str, amount_str) if currency.position == 'before' else (amount_str, currency_str))
        result = u"""

%s

                             %s

                         %%s

""" % (amount, payment_header)
        return result % html_block.decode("utf-8")

    def render_payment_block(self, cr, uid, reference, amount, currency_id, tx_id=None, partner_id=False, partner_values=None, tx_values=None, company_id=None, context=None):
        html_forms = []
        domain = [('website_published', '=', True), ('validation', '=', 'automatic')]
        if company_id:
            domain.append(('company_id', '=', company_id))
        acquirer_ids = self.search(cr, uid, domain, context=context)
        for acquirer_id in acquirer_ids:
            button = self.render(
                cr, uid, acquirer_id,
                reference, amount, currency_id,
                tx_id, partner_id, partner_values, tx_values,
                context)
            html_forms.append(button)
        if not html_forms:
            return ''
        html_block = '\n'.join(filter(None, html_forms))
        return self._wrap_payment_block(cr, uid, html_block, amount, currency_id, context=context)

class PaymentTransaction(osv.Model):
    """ Transaction Model. Each specific acquirer can extend the model by adding
    its own fields.

    Methods that can be added in an acquirer-specific implementation:

     - ``<name>_create``: method receiving values used when creating a new
       transaction and that returns a dictionary that will update those values.
       This method can be used to tweak some transaction values.

    Methods defined for convention, depending on your controllers:

     - ``<name>_form_feedback(self, cr, uid, data, context=None)``: method that
       handles the data coming from the acquirer after the transaction. It will
       generally receives data posted by the acquirer after the transaction.
    """
    _name = 'payment.transaction'
    _description = 'Payment Transaction'
    _inherit = ['mail.thread']
    _order = 'id desc'
    _rec_name = 'reference'

    _columns = {
        'date_create': fields.datetime('Creation Date', readonly=True, required=True),
        'date_validate': fields.datetime('Validation Date'),
        'acquirer_id': fields.many2one(
            'payment.acquirer', 'Acquirer',
            required=True,
        ),
        'type': fields.selection(
            [('server2server', 'Server To Server'), ('form', 'Form')],
            string='Type', required=True),
        'state': fields.selection(
            [('draft', 'Draft'), ('pending', 'Pending'),
             ('done', 'Done'), ('error', 'Error'),
             ('cancel', 'Canceled')
             ], 'Status', required=True,
            track_visiblity='onchange', copy=False),
        'state_message': fields.text('Message',
                                     help='Field used to store error and/or validation messages for information'),
        # payment
        'amount': fields.float('Amount', required=True,
                               digits=(16, 2),
                               track_visibility='always',
                               help='Amount in cents'),
        'fees': fields.float('Fees',
                             digits=(16, 2),
                             track_visibility='always',
                             help='Fees amount; set by the system because depends on the acquirer'),
        'currency_id': fields.many2one('res.currency', 'Currency', required=True),
        'reference': fields.char('Order Reference', required=True),
        'acquirer_reference': fields.char('Acquirer Order Reference',
                                          help='Reference of the TX as stored in the acquirer database'),
        # duplicate partner / transaction data to store the values at transaction time
        'partner_id': fields.many2one('res.partner', 'Partner', track_visibility='onchange',),