"""
Contains functionality relate to interfacing wiggly and puq
"""
import binascii,os,re
import numpy as np
import matplotlib.pyplot as plt
import shapely.geometry
import CrispObjects,FuzzyObjects,fuzz,utilities
import _distributions as distributions
import puq

class ObjectManager(object):
    """
    Manages Wiggly objects.

    - *ignore_certain_vertices*: Used in the SA module to limit the number of runs required
      when there are partially certain objects.

      If True, then certain vertices (or edges) which would otherwise be added to puq as
      ConstantParameters are not added at all.

      ================ =============================================================================
      Object Type       Behavior
      ================ =============================================================================
      Rigid            No effect. x,y,theta are included in the SA even if they are certain.

      deformable       Non-uncertain vertices are not included in the SA

      vertex-defined   Non-uncertain vertices are not included in the SA (todo)

      edge-defined     Non-uncertain edges are not included in the SA (todo)
      ================ =============================================================================
    """

    def __init__(self,ignore_certain_vertices=False):


        self._objects={}    #keys are object names, values are dictionaries with keys:values
                            # 'obj':FObject 'group':the group obj belongs to
                            # 'tag':any additional information about obj
        self._groups={}     #keys are group names, values are lists of object names
        self._classes={}    #keys are class names, values are lists of object names

        self._shapesFileFirstWrite=True
        self._ignore_certain_vertices=ignore_certain_vertices

    @staticmethod
    def isShapeParam(paramname):
        """
        Given a parameter name, checks whether this parameter indicates that it belongs
        to a shape.

        - *paramname*: a string indicating the parameter name to check

        Returns: true if this parameter name indicates that it is part of a shape
        """
        if paramname!=None:
            if str(paramname).startswith("wXY__"):
                return True
            else:
                return False
        else:
            return False

    @staticmethod
    def isShapeParamLegacy(paramname):
        """
        Given a legacy parameter name, checks whether this parameter indicates that it belongs
        to a shape.

        - *paramname*: a string indicating the parameter name to check

        Returns: true if this parameter name indicates that it is part of a shape.

        After changing the wiggly parameter separator to __ instead of _, this
        function is needed to check whether the separators should be __ instead of _
        when parsing the parameters into shapes.
        """
        if paramname!=None:
            if str(paramname).startswith("wXY_"):
                return True
            else:
                return False
        else:
            return False

    @staticmethod
    def getBaseShapes(shapesFileName='shapes.json'):
        """
        Constructs shapely shapes out of a previously saved shapes file generated by
        :func:`ObjectManager.crispObjects2PuqParams` or :func:`ObjectManager.fuzzyObjects2PuqParams`.

        Returns a dictionary in the same format as :func:`ObjectManager.puq2Shapes`.
        """
        if not os.path.isfile(shapesFileName):
            utilities.msg('getBaseShapes:{} not found. No shapes generated'.format(shapesFileName),'w')
            return {}

        f=open(shapesFileName,'r')
        baseShapesDict=puq.unpickle(f.read())

        shapes={}

        #search for all params corresponding to this shape
        for shpName,shpItem in baseShapesDict.iteritems():
            shp_rt=None
            if len(shpItem['pts_x'])==1:
                #point
                shp_rt=shapely.geometry.Point(shpItem['pts_x'],shpItem['pts_y'])
            elif shpItem['isClosed']:
                #polygon
                shp_rt=shapely.geometry.Polygon(np.vstack((shpItem['pts_x'],shpItem['pts_y'])).T)
            else:
                #linestring
                shp_rt=shapely.geometry.LineString(np.vstack((shpItem['pts_x'],shpItem['pts_y'])).T)

            if shp_rt!=None:
                shapes[shpName]={'shp':shp_rt,'desc':'base shape','type':shpItem['type']}
        return shapes

    @staticmethod
    def puq2Shapes(shapesFileName='shapes.json',params=None,paramsFileName=None):
        """
        Constructs distorted shapes out of a puq parameters, using a set of base shapes.


        - *shapesFileName*: a file name containing the base shapes. See :func:`crispObjects2PuqParams`
          for more information.
        - *params*: a list of dictionaries. Each dictionary contains the keys 'name',
          'desc', 'value'. Key 'name' is the puq param name constructed using the \*2puqParams methods.
          'desc' (may be None) is the description and 'value' is a single value
          of the parameter.

              [ {'name':<string>, 'desc':<string>, 'value':<float>}, ... ]

          See :func:`crispObjects2PuqParams` for details on the convention for 'name'.

        - *paramsFileName*: If set, *params* is read from file. See the paramsByFile option of
          the puq TestProgram documentation.

        Returns: a dictionary with keys equal to the parameter name. The parameter name is the
        original name, extracted from the name generated by \*2puqParams. Values are dictionaries
        with the following key:value pairs -- 'shp':Shapely shape 'desc':description.

            {<paramName string>, {'shp':<Shapely object>, 'desc':<string>,
            'type':<string>} 'alphacut':<float or None>, ...}

        'type' is the type of object: 'D'=deformable, 'R'=rigid, 'V'=vertex-defined, 'E'=edge-defined.
        Any parameters which weren't constructed with :func:`crispObjects2PuqParams` or
        :func:`fuzzyObjects2PuqParams` are ignored.
        """
        shapes={}

        if paramsFileName!=None:
            if params!=None:
                print('paramsFileName set. Ignoring params')

            #paramvalues is a list of tuples.
            paramValues=np.loadtxt(paramsFileName,
                           dtype={"names":("p_name","p_val","p_desc"),
                                  "formats":(np.object,np.float,np.object)})
            params=[]
            for tupleParam in paramValues:
                params.append({'name':tupleParam[0], 'value':tupleParam[1],
                                   'desc':tupleParam[2]})
        else:
            if params==None:
                raise Exception("Must specify either params or paramsFileName")

        #Parse object metadata from the puq parameter name.
        #data contains the parsed information:
        #key=name value=dictionary with
        #   'index':list of int
        #   'varType':list of string
        #   'alphacut':list of float. (Empty list for probabilistic parameters)
        #   'desc': list of string
        #   'value': list of float
        data={}
        for param in params:
            if param['name']!=None:
                if ObjectManager.isShapeParam(param['name']) or ObjectManager.isShapeParamLegacy(param['name']):
                    if ObjectManager.isShapeParam(param['name']):
                        parse=param['name'].split('__')
                    else:
                        #for files which were made before the switch to double underscores
                        parse=param['
name'].split('_')

                    #the parameter has been verified to come from *2PuqParams.
                    #do more validation
                    s
kipped=False

                    if len(parse)<4 or len(parse)>5:
                        print("Error: parameter {} isn't in the proper format.".format(param['name']))
                        skipped=True
                    elif parse[2]!='x' and parse[2]!='y' and parse[2]!='t' and parse[2]!='e':
                        print("Error