###############################################################################
##
## Copyright (C) 2014-2015, New York University.
## Copyright (C) 2011-2014, NYU-Poly.
## Copyright (C) 2006-2011, University of Utah.
## All rights reserved.
## Contact: contact@vistrails.org
##
## This file is part of VisTrails.
##
## "Redistribution and use in source and binary forms, with or without
## modification, are permitted provided that the following conditions are met:
##
##  - Redistributions of source code must retain the above copyright notice,
##    this list of conditions and the following disclaimer.
##  - Redistributions in binary form must reproduce the above copyright
##    notice, this list of conditions and the following disclaimer in the
##    documentation and/or other materials provided with the distribution.
##  - Neither the name of the New York University nor the names of its
##    contributors may be used to endorse or promote products derived from
##    this software without specific prior written permission.
##
## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
## AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
## THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
## PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
## CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
## EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
## PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
## OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
## WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
## OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
## ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
##
###############################################################################
from __future__ import division

from itertools import izip
import operator

from vistrails.core.data_structures.bijectivedict import Bidict
from vistrails.core.modules.utils import create_port_spec_string, parse_port_spec_string
from vistrails.core.system import get_vistrails_basic_pkg_id, \
    get_module_registry
from vistrails.core.utils import enum, VistrailsInternalError
from vistrails.core.vistrail.port_spec_item import PortSpecItem
from vistrails.db.domain import DBPortSpec, IdScope

from ast import literal_eval
import unittest
import copy

PortEndPoint = enum('PortEndPoint',
                    ['Invalid', 'Source', 'Destination'])

################################################################################

class PortSpec(DBPortSpec):

    port_type_map = Bidict([('input', 'destination'),
                            ('output', 'source'),
                            ('invalid', 'invalid')])
    end_point_map = Bidict([('source', PortEndPoint.Source),
                            ('destination', PortEndPoint.Destination),
                            ('invalid', PortEndPoint.Invalid)])

    ##########################################################################
    # Constructors and copy

    def __init__(self, *args, **kwargs):
        signature = None
        if 'signature' in kwargs:
            signature = kwargs['signature']
            del kwargs['signature']
        sigstring = None
        if 'sigstring' in kwargs:
            sigstring = kwargs['sigstring']
            del kwargs['sigstring']
        defaults = None
        if 'defaults' in kwargs:
            defaults = kwargs['defaults']
            del kwargs['defaults']
        labels = None
        if 'labels' in kwargs:
            labels = kwargs['labels']
            del kwargs['labels']
        values = None
        if 'values' in kwargs:
            values = kwargs['values']
            del kwargs['values']
        entry_types = None
        if 'entry_types' in kwargs:
            entry_types = kwargs['entry_types']
            del kwargs['entry_types']

        if 'items' in kwargs and 'portSpecItems' not in kwargs:
            kwargs['portSpecItems'] = kwargs['items']
            del kwargs['items']

        if 'optional' not in kwargs:
            kwargs['optional'] = 0 # False
        elif not isinstance(kwargs['optional'], (int, long)):
            if isinstance(kwargs['optional'], bool):
                if kwargs['optional']:
                    kwargs['optional'] = 1
                else:
                    kwargs['optional'] = 0
            else:
                raise VistrailsInternalError("Cannot parse 'optional' kw "
                                             "-- must be an int or bool")
        if 'min_conns' not in kwargs:
            kwargs['min_conns'] = 0
        elif kwargs['optional'] == 1 and kwargs['min_conns'] > 0:
            raise VistrailsInternalError("A mandatory port cannot be set "
                                         "to optional")
        if 'max_conns' not in kwargs:
            kwargs['max_conns'] = -1
        if kwargs['min_conns'] >= 0 and kwargs['max_conns'] >= 0 and \
                kwargs['min_conns'] > kwargs['max_conns']:
            raise VistrailsInternalError("Minimum number of connections "
                                         "cannot be greater than maximum "
                                         "number of connections")
            
        if 'sort_key' not in kwargs:
            kwargs['sort_key'] = -1
        if 'depth' not in kwargs:
            kwargs['depth'] = 0
        if 'id' not in kwargs:
            kwargs['id'] = -1
        if 'tooltip' in kwargs:
            self._tooltip = kwargs['tooltip']
            del kwargs['tooltip']
        else:
            self._tooltip = None

        if 'docstring' in kwargs:
            self._docstring = kwargs['docstring']
            del kwargs['docstring']
        else:
            self._docstring = None
        if 'shape' in kwargs:
            self._shape = kwargs['shape']
            del kwargs['shape']
        else:
            self._shape = None

        DBPortSpec.__init__(self, *args, **kwargs)

        if sum(1 for container in (self.port_spec_items, signature, sigstring)
               if container) > 1:
            raise ValueError("Please specify only one of portSpecItems,"
                             " signature, or sigstring kwargs.")
        self.create_spec_items(self.port_spec_items, signature, sigstring, 
                               defaults, labels, values, entry_types)

        self._short_sigstring = None
        # if signature is not None:
        #     self.create_entries(signature)
        # if not self.sigstring and self._entries is not None:
        #     # create sigstring from entries
        #     self.create_sigstring_and_descriptors()
# DAKOOP: removed this---we will check in module_registry and pipeline 
# validation, this way, we can let errors go all the way up
#         elif self._entries is None and self.sigstring:
#             # create entries from sigstring
#             self.create_entries_and_descriptors()
#         else:
#             raise VistrailsInternalError("Need to specify signature or "
#                                          "sigstring to create PortSpec")
        # if self._entries is not None and self._tooltip is None:
        #     self.create_tooltip()
        self.is_valid = True

    def __copy__(self):
        return PortSpec.do_copy(self)

    def do_copy(self, new_ids=False, id_scope=None, id_remap=None):
        cp = DBPortSpec.do_copy(self, new_ids, id_scope, id_remap)
        cp._short_sigstring = self._short_sigstring
        cp._tooltip = self._tooltip
        cp._shape = self._shape
        cp._docstring = self._docstring
        cp.is_valid = self.is_valid
        cp.__class__ = PortSpec
        # if cp._entries is not None:
        #     cp.create_tooltip()
        return cp

    @staticmethod
    def convert(_port_spec):
        if _port_spec.__class__ == PortSpec:
            return
        _port_spec.__class__ = PortSpec
        for _port_spec_item in _port_spec.db_portSpecItems:
            PortSpecItem.convert(_port_spec_item)
        _port_spec._short_sigstring = None
        _port_spec._tooltip = None
        _port_spec._shape = None
        _port_spec._docstring = None
        _port_spec.is_valid = True

        _port_spec.port_spec_items.sort(key=operator.attrgetter('db_pos'))

    @staticmethod
    def from_sigstring(sigstring):
        """from_sig(sigstring: string) -> PortSpec

        Returns a portspec from the given sigstring.

        """
        return PortSpec(sigstring=sigstring)

    ##########################################################################
    # Properties

    id = DBPortSpec.db_id
    name = DBPortSpec.db_name
    type = DBPortSpec.db_type
    optional = DBPortSpec.db_optional
    sort_key = DBPortSpec.db_sort_key
    min_conns = DBPortSpec.db_min_conns
    max_conns = DBPortSpec.db_max_conns
    _depth = DBPortSpec.db_depth
    port_spec_items = DBPortSpec.db_portSpecItems
    items = DBPortSpec.db_portSpecItems

    def _get_sigstring(self):
        return create_port_spec_string([i.spec_tuple 
                                        for i in self.port_spec_items])
    sigstring = property(_get_sigstring)

    def is_mandatory(self):
        return (self.min_conns > 0)

    def _get_labels(self):
        return [i.label for i in self.port_spec_items]
    labels = property(_get_labels)

    def _get_defaults(self):
        return [i.default for i in self.port_spec_items]
    defaults = property(_get_defaults)
    
    def _get_short_sigstring(self):
        if self._short_sigstring is None:
            self.create_tooltip()
        return self._short_sigstring
    short_sigstring = property(_get_short_sigstring)

    def _get_signature(self):
        signature = []
        for i in self.port_spec_items:
            signature.append((i.descriptor.module, i.label))
        return signature
    signature = property(_get_signature)

    def _get_depth(self):
        return self._depth or 0
    def _set_depth(self, depth):
        self._depth = depth
    depth = property(_get_depth, _set_depth)

    def toolTip(self):
        if self._tooltip is None:
            self.create_tooltip()
        return self._tooltip

    def shape(self):
        return self._shape
    
    def docstring(self):
        return self._docstring

    def descriptors(self):
        return [i.descriptor for i in self.port_spec_items]

    ##########################################################################
    # Methods

    def _resize_attrs(self, target, *lists):
        for rlist in lists:
            if len(target) > len(rlist):
                rlist.extend(None for i in xrange(len(target)-len(rlist)))

    def _set_attrs(self, item, *attrs):
        attr_order = ['default', 'label', 'values', 'entry_type']
        if item is None:
            kwargs = dict(izip(attr_order, attrs))
            return kwargs
        else:
            for (attr_key, attr) in izip(attr_order, attrs):
                if attr is not None:
                    setattr(item, attr_key, attr)


    def create_spec_items(self, items=None, signature=None, sigstring=None, 
                          defaults=None, labels=None, values=None, 
                          entry_types=None):
        if defaults is None:
            defaults = []
        elif isinstance(defaults, basestring):
            defaults = literal_eval(defaults)
        if labels is None:
            labels = []