# -*- coding: utf-8 -*-
""" ChemconnectUtil.py Some of the command function are written """

__author__           = "Harsha Rani"
__copyright__        = "Copyright 2017, Harsha Rani and NCBS Bangalore"
__credits__          = ["NCBS Bangalore"]
__license__          = "GNU GPL"
__version__          = "1.0.0"
__maintainer__       = "Harsha Rani"
__email__            = "hrani@ncbs.res.in"
__status__           = "Development"
__updated__          = "Mar 11 2019"

import moose
import numpy as np
import pickle
import matplotlib
import os
import random
import re

direct = os.path.dirname(__file__)
colormap_file = open(os.path.join(direct, "rainbow2.pkl"), "r")  
colorMap = pickle.loads(colormap_file.read().encode('utf8'))
colormap_file.close()
ignoreColor= ["mistyrose","antiquewhite","aliceblue","azure","bisque","black","blanchedalmond","blue","cornsilk","darkolivegreen","darkslategray","dimgray","floralwhite","gainsboro","ghostwhite","honeydew","ivory","lavender","lavenderblush","lemonchiffon","lightcyan","lightgoldenrodyellow","lightgray","lightyellow","linen","mediumblue","mintcream","navy","oldlace","papayawhip","saddlebrown","seashell","snow","wheat","white","whitesmoke","aquamarine","lightsalmon","moccasin","limegreen","snow","sienna","beige","dimgrey","lightsage"]
matplotcolor = {}
for name,hexno in matplotlib.colors.cnames.items():
    matplotcolor[name]=hexno

def getRandColor():
    k = random.choice(list(matplotcolor.keys()))
    if k in ignoreColor:
        return getRandColor()
    else:
        #return matplotcolor[k]
        return str(matplotlib.colors.cnames[k])

def getColor(iteminfo):
    """ Getting a textcolor and background color for the given  mooseObject \
        If textcolor is empty replaced with green \
           background color is empty replaced with blue
           if textcolor and background is same as it happend in kkit files \
           replacing textcolor with random color\
           The colors are not valid there are siliently replaced with some values \
           but while model building can raise an exception
    """
    textcolor = moose.element(iteminfo).textColor
    bgcolor   = moose.element(iteminfo).color
    if(textcolor == ''): textcolor = 'green'
    
    if(bgcolor == ''): bgcolor = 'orange'

    if(textcolor == bgcolor):
        textcolor = getRandColor()

    textcolor = colorCheck(textcolor)
    bgcolor = colorCheck(bgcolor)
    # if moose.exists(iteminfo):
    #     moose.element(iteminfo).textColor = textcolor
    # moose.element(iteminfo).color = bgcolor
    return(textcolor,bgcolor)

def colorCheck(fc_bgcolor):
    """ textColor or background can be anything like string or tuple or list \
        if string its taken as colorname further down in validColorcheck checked for valid color, \
        but for tuple and list its taken as r,g,b value.
    """
    if isinstance(fc_bgcolor,str):
        if fc_bgcolor.startswith("#"):
            fc_bgcolor = fc_bgcolor
        elif (fc_bgcolor.isdigit()):
            """ color is int  a map from int to r,g,b triplets from pickled color map file """
            tc = (int(fc_bgcolor))*2
            if tc < len(colorMap):
                pickledColor = colorMap[tc]
            else:
                pickledColor = (255, 0, 0)
            fc_bgcolor = '#%02x%02x%02x' % (pickledColor)

        elif fc_bgcolor.isalpha() or fc_bgcolor.isalnum():
            fc_bgcolor = validColorcheck(fc_bgcolor)
            
        else:
            for r in ['[',']','(',')']:
                fc_bgcolor = fc_bgcolor.replace(r,"")
            fc_bgcolor = fc_bgcolor.split(",")
            c = 0
            hexlist ="#"
            for n in fc_bgcolor:
                if c < 3:
                    hexlist = hexlist+str("%02x" % int(n))
                    c = c+1;
            fc_bgcolor = hexlist
        return(fc_bgcolor)

def validColorcheck(color):
    ''' 
        Both in Qt4.7 and 4.8 if not a valid color it makes it as back but in 4.7 there will be a warning mssg which is taken here
        checking if textcolor or backgroundcolor is valid color, if 'No' making white color as default
        where I have not taken care for checking what will be backgroundcolor for textcolor or textcolor for backgroundcolor 
    '''
    #if QColor(color).isValid():
    if matplotlib.colors.is_color_like(color):
        if color == "blue":
            color = "orange"
        color =  matplotlib.colors.cnames[color.lower()]
        return color
    else:
        return(matplotlib.colors.cnames["orange"])

def xyPosition(objInfo,xory):
    if objInfo is None:
        return 0.0
    try:
        return float(moose.element(objInfo).getField(xory))
    except ValueError as e:
        return float(0)

def setupMeshObj(modelRoot):
    ''' Setup compartment and its members pool,reaction,enz cplx under self.meshEntry dictionaries \
    self.meshEntry with "key" as compartment,
    value is key2:list where key2 represents moose object type,list of objects of a perticular type
    e.g self.meshEntry[meshEnt] = { 'reaction': reaction_list,'enzyme':enzyme_list,'pool':poollist,'cplx': cplxlist }
    '''
    xmin = 0.0
    xmax = 1.0
    ymin = 0.0
    ymax = 1.0
    listOfitems = {}
    positionInfoExist = True
    meshEntry = {}
    if meshEntry:
        meshEntry.clear()
    else:
        meshEntry = {}
    xcord = []
    ycord = []
    meshEntryWildcard = '/##[ISA=ChemCompt]'
    if modelRoot != '/':
        meshEntryWildcard = modelRoot+meshEntryWildcard
    for meshEnt in moose.wildcardFind(meshEntryWildcard):
        mollist  = []
        realist  = []
        enzlist  = []
        cplxlist = []
        tablist  = []
        funclist = []

        mol_cpl  = moose.wildcardFind(meshEnt.path+'/##[ISA=PoolBase]')
        funclist = moose.wildcardFind(meshEnt.path+'/##[ISA=Function]')
        enzlist  = moose.wildcardFind(meshEnt.path+'/##[ISA=EnzBase]')
        realist  = moose.wildcardFind(meshEnt.path+'/##[ISA=ReacBase]')
        tablist  = moose.wildcardFind(meshEnt.path+'/##[ISA=StimulusTable]')
        if mol_cpl or funclist or enzlist or realist or tablist:
            for m in mol_cpl:
                objInfo = None
                # if m.parent.isA['CplxEnzBase']: # New bindings
                if moose.element(m.parent).isA['CplxEnzBase']:
                    cplxlist.append(m)
                    objInfo = m.parent.path+'/info'
                elif m.isA['PoolBase']:
                    mollist.append(m)
                    objInfo =m.path+'/info'
                if objInfo is not None and moose.exists(objInfo):
                    listOfitems[moose.element(moose.element(objInfo).parent)]={'x':xyPosition(objInfo,'x'),'y':xyPosition(objInfo,'y')}

                xcord.append(xyPosition(objInfo,'x'))
                ycord.append(xyPosition(objInfo,'y'))

            getxyCord(xcord,ycord,funclist,listOfitems)
            getxyCord(xcord,ycord,enzlist,listOfitems)
            getxyCord(xcord,ycord,realist,listOfitems)
            getxyCord(xcord,ycord,tablist,listOfitems)

            meshEntry[meshEnt] = {'enzyme':enzlist,
                                  'reaction':realist,
                                  'pool':mollist,
                                  'cplx':cplxlist,
                                  'table':tablist,
                                  'function':funclist
                                  }
            positionInfoExist = not(len(np.nonzero(xcord)[0]) == 0 \
                and len(np.nonzero(ycord)[0]) == 0)
            if positionInfoExist:
                xmin = min(xcord)
                xmax = max(xcord)
                ymin = min(ycord)
                ymax = max(ycord)
    return meshEntry,xmin,xmax,ymin,ymax,positionInfoExist,listOfitems
            
def getxyCord(xcord,ycord,list1,listOfitems):
    for item in list1:
        # if isinstance(item,Function):
        #     objInfo = moose.element(item.parent).path+'/info'
        # else:
        #     objInfo = item.path+'/info'
        #if not isinstance(item,moose.Function):
        if not item.isA["Function"]:
            objInfo = item.path+'/info'
            xcord.append(xyPosition(objInfo,'x'))
            ycord.append(xyPosition(objInfo,'y'))
            if moose.exists(objInfo):
                listOfitems[moose.element(moose.element(objInfo).parent)]={'x':xyPosition(objInfo,'x'),'y':xyPosition(objInfo,'y')}

def setupItem(modelPath,cntDict):
    '''This function collects information of what is connected to what. \
    eg. substrate and product connectivity to reaction's and enzyme's \
    sumtotal connectivity to its pool are collected '''
    #print " setupItem"
    sublist = []
    prdlist = []
    zombieType = ['ReacBase','EnzBase','Function','StimulusTable']
    for baseObj in zombieType:
        path = '/##[ISA='+baseObj+']'
        if modelPath != '/':
            path = modelPath+path
        if ( (baseObj == 'ReacBase') or (baseObj == 'EnzBase')):
            for items in moose.wildcardFind(path):
                sublist = []
                prdlist = []
                uniqItem,countuniqItem = countitems(items,'subOut')
                subNo = uniqItem
                for sub in uniqItem:
                    sublist.append((moose.element(sub),'s',countuniqItem[sub]))

                uniqItem,countuniqItem = countitems(items,'prd')
                prdNo = uniqItem
                if (len(subNo) == 0 or len(prdNo) == 0):
                    print ("\nSubstrate Product is empty for "+items.path)

                for prd in uniqItem:
                    prdlist.append((moose.element(prd),'p',countuniqItem[prd]))

                if (baseObj == 'CplxEnzBase') :
                    uniqItem,countuniqItem = countitems(items,'toEnz')
                    for enzpar in uniqItem:
                        sublist.append((moose.element(enzpar),'t',countuniqItem[enzpar]))

                    uniqItem,countuniqItem = countitems(items,'cplxDest')
                    for cplx in uniqItem:
                        prdlist.append((moose.element(cplx),'cplx',countuniqItem[cplx]))

                if (baseObj == 'EnzBase'):
                    uniqItem,countuniqItem = countitems(items,'enzDest')
                    for enzpar in uniqItem:
                        sublist.append((moose.element(enzpar),'t',countuniqItem[enzpar]))
                cntDict[items] = sublist,prdlist
        elif baseObj == 'Function':
            for items in moose.wildcardFind(path):
                sublist = []
                prdlist = []
                item = items.path+'/x[0]'
                uniqItem,countuniqItem = countitems(item,'input')
                for funcpar in uniqItem:
                    sublist.append((moose.element(funcpar),'sts',countuniqItem[funcpar]))

                uniqItem,countuniqItem = countitems(items,'valueOut')
                for funcpar in uniqItem:
                    prdlist.append((moose.element(funcpar),'stp',countuniqItem[funcpar]))
                cntDict[items] = sublist,prdlist

        # elif baseObj == 'Function':
        #     #ZombieSumFunc adding inputs
        #     inputlist = []
        #     outputlist = []
        #     funplist = []
        #     nfunplist = []
        #     for items in moose.wildcardFind(path):
        #         for funplist in moose.element(items).neighbors['valueOut']:
        #             for func in funplist:
        #                 funcx = moose.element(items.path+'/x[0]')
        #                 uniqItem,countuniqItem = countitems(funcx,'input')
        #                 for inPut in uniqItem:
        #                     inputlist.append((inPut,'st',countuniqItem[inPut]))
        #             cntDict[func] = inputlist
        else:
            for tab in moose.wildcardFind(path):
                tablist = []
                uniqItem,countuniqItem = countitems(tab,'output')
                for tabconnect in uniqItem:
                    tablist.append((moose.element(tabconnect),'tab',countuniqItem[tabconnect]))
                cntDict[tab] = tablist

def countitems(mitems,objtype):
    items = []
    items = moose.element(mitems).neighbors[objtype]
    uniqItems = set(items)
    #countuniqItems = Counter(items)
    countuniqItems = dict((i, items.count(i)) for i in items)
    return(uniqItems,countuniqItems)

def findCompartment(elem):
    element = moose.element(elem)
    if element.path == '/':
        return element
    if element.isA['ChemCompt']:
        return element
    return findCompartment(element.parent)

def mooseIsInstance(elem, classes):
    for cl in classes:
        if elem.isA[cl]:
            return True
    return False
