'''This file is part of AToMPM - A Tool for Multi-Paradigm Modelling
Copyright 2011 by the AToMPM team and licensed under the LGPL
See COPYING.lesser and README.md in the root of this project for full details'''

import copy, subprocess, traceback, os
from .pytcore.core.himesis import HConstants as HC
from .utils import Utilities as utils

'''
    implements the DesignerAPI functions used in pattern/attribute constraints 
    and actions

    NOTE: the current username is required (and remembered) to enable file I/O
              within his own directory... the current backend aswid is required (and
            remembered) to enable httpreqs to backend from rule code

    NOTE: cloned from mmmk.js constraint/action/accessor DesignerAPI... same
              comments and thoughts apply... noteworthy differences are 
                . instead of atompm ids, API functions now take __pLabels as node
                    identifiers
                . 'sys_*()' functions enable system calls and file i/o
                . 'session_*()' functions provide a clean and efficient way to 
                  remember information across rules
                . the 'isConnectionType()' function '''
class DesignerAPI :
    def __init__(self,username,aswid,mtwid) :
        self._username = username
        self._aswid        = aswid
        self._mtwid        = mtwid

    def _aswPrintReq(self,msg):
        utils.httpReq(    'PUT',
                          '127.0.0.1:8124',
                          '/GET/console?wid='+self._aswid,
                          {'text':msg})

    def _printToDevCon(self,msg):
        self._aswPrintReq(msg)


    '''
        configure this instance of DesignerAPI

        1. set the Himesis graph that will be used to satisfy getAttr/setAttr/...
            function calls... this graph represents a (partially) matched LHS, NAC
            or RHS 
        2. set the type of the calling function... legal values are 
                patternCondition
                patternAction
                attrCondition
                attrAction
        3. set the pattern-label -> graph-index mapping 
        4. set reference to exception holder
        5. set the __pLabel of the owner of the calling function, if any
        6. set the attribute that owns the calling function, if any 
        7. set reference to journal (to write changes to), if any 
        8. extend the pattern-label -> graph-index map w/ $atompmId -> graph-index
            mappings... this enables access to all graph nodes (not just matched 
            ones, that are incidently the only ones w/ associated __pLabels) 
        
        TBI: self._pl2gi should be given a name that reflects step 8. and errors
              pertaining to missing/incorrect pLabel parameters should be updated 
              to account for fact that specified pLabels may not be pLabels (i.e.,
              they could be of the form '$atompmId:*' '''
    def configure(self,graph,type,pl2gi,ex,pLabel=None,attr=None,journal=None) :
        self._graph     = graph
        self._type         = type
        self._pl2gi        = pl2gi
        self._ex            = ex
        self._pLabel     = pLabel
        self._attr       = attr
        self._journal     = journal

        matched = list(self._pl2gi.values())
        for v in self._graph.vs :
            if v.index not in matched :
                self._pl2gi['$atompmId:'+str(v['$atompmId'])] = v.index


    '''
        wrapper around raise()... to be properly reported to the client, 
        exceptions can not simply be raised... this would just cause them to be
          captured by their ExecutionContext who might return a generic error (not
        the one it caught)... instead, we store exceptions, if any, in self._ex 
        for the caller to do with them what he wants... '''
    def __raise(self,msg) :
        self._ex['$err'] = msg
        print("Error: " + str(msg))
        raise RuntimeError(msg)



    # ***************************** API functions ***************************** #
    def _getAttr(self,attr=None,pLabel=None) :
        if pLabel == None :
            if self._type.startswith('pattern') :
                self.__raise( \
                    'getAttr() requires a __pLabel in pattern conditions/actions')
            pLabel = self._pLabel
        elif not self._type.startswith('pattern') :
            self.__raise( \
                'getAttr() only accepts a __pLabel in pattern conditions/actions')
        elif pLabel not in self._pl2gi :
            self.__raise( \
                'invalid getAttr() __pLabel :: '+str(pLabel)+' (either no node '+ \
                'with that __pLabel exists, or none is matched yet)')
        if attr == None :
            if not self._type.startswith('attr') :
                self.__raise( \
                    'getAttr() can only be called without parameters'+ \
                    'in attribute conditions/actions')
            attr = self._attr

        n = self._graph.vs[self._pl2gi[pLabel]]
        if attr not in n.attribute_names() :
            self.__raise('invalid getAttr() attribute :: '+str(attr))
        return copy.deepcopy(n[attr])


    def _getAttrNames(self,pLabel=None):
        if pLabel == None :
            if self._type.startswith('pattern') :
                self.__raise( \
                    'getAttrNames() requires a __pLabel in pattern conditions/actions')
            pLabel = self._pLabel
        elif not self._type.startswith('pattern') :
            self.__raise( \
                'getAttrNames() only accepts a __pLabel in pattern conditions/actions')
        elif pLabel not in self._pl2gi :
            self.__raise( \
                'invalid getAttr() __pLabel :: '+str(pLabel)+' (either no node '+ \
                'with that __pLabel exists, or none is matched yet)')

        n = self._graph.vs[self._pl2gi[pLabel]]
        return n.attribute_names()


    def _hasAttr(self,attr=None,pLabel=None) :
        if pLabel == None :
            if self._type.startswith('pattern') :
                self.__raise( \
                    'hasAttr() requires a __pLabel in pattern conditions/actions')
            pLabel = self._pLabel
        elif not self._type.startswith('pattern') :
            self.__raise( \
                'hasAttr() only accepts a __pLabel in pattern conditions/actions')
        elif pLabel not in self._pl2gi :
            self.__raise( \
                'invalid hasAttr() __pLabel :: '+str(pLabel)+' (either no node '+ \
                'with that __pLabel exists, or none is matched yet)')
        if attr == None :
            self.__raise( \
                'hasAttr() can not be called without an attribute parameter')

        n = self._graph.vs[self._pl2gi[pLabel]]
        return attr in n.attribute_names()



    def _setAttr(self,attr,val,pLabel) :
        if self._type != 'patternAction' :
            self.__raise('setAttr() can only be used within RHS actions')
        elif pLabel == None :
            self.__raise('setAttr() requires a valid __pLabel')
        elif pLabel not in self._pl2gi :
            self.__raise( \
                'invalid setAttr() __pLabel :: '+str(pLabel)+' (either no node '+ \
                'with that __pLabel exists, or none is matched yet)')

        n = self._graph.vs[self._pl2gi[pLabel]]
        if attr not in n.attribute_names() :
            self.__raise('invalid setAttr() attribute :: '+attr)
        oldVal = n[attr]
        if oldVal != val :
            n[attr] = val
            self._journal.append(
                {'op':'CHATTR',
                 'guid':n[HC.GUID],
                 'attr':attr,
                 'old_val':oldVal,
                 'new_val':val})
            n[HC.MT_DIRTY] = True



    def _getAllNodes(self,fulltypes=None) :
        if not self._type.startswith('pattern') :
            self.__raise( \
                'getAllNodes() can only be used in pattern conditions/actions')
        elif fulltypes != None and fulltypes.__class__ != [].__class__ :
            self.__raise('invalid getAllNodes() fulltypes array :: '+fulltypes)

        pLabels = []
        for pLabel in self._pl2gi :
            n = self._graph.vs[self._pl2gi[pLabel]]
            if fulltypes == None or n[HC.FULLTYPE] in fulltypes :
                pLabels.append(pLabel)
        return pLabels

    def _getNodesFromLabels(self, pLabels):
        nodes = [self._graph.vs[self._pl2gi[pLabel]] for pLabel in pLabels]
        return nodes

    def _getNeighbors(self,dir,type,pLabel) :
        if not self._type.startswith('pattern') :
            self.__raise( \
                'getNeighbors() can only be used in pattern conditions/actions')
        elif pLabel == None :
            self.__raise('getNeighbors() requires a valid __pLabel')
        elif pLabel not in self._pl2gi :
            self.__raise( \
                'invalid getNeighbors() __pLabel :: '+str(pLabel)+' (no node '+ \
                'with that __pLabel exists)')

        pLabels = set()
        if len(self._graph.es) > 0 :
            gi2pl = dict((v, k) for (k, v) in list(self._pl2gi.items()))
            idx   = self._pl2gi[pLabel]
            for e in self._graph.get_edgelist() :
                if e[0] == idx and \
                        (dir == '>' or dir == '*' or dir == "out") and \
                        (type == '*' or self._graph.vs[e[1]][HC.FULLTYPE] == type) :
                    pLabels.add(gi2pl[e[1]])
                elif e[1] == idx and \
                        (dir == '<' or dir == '*' or dir == "in") and \
                        (type == '*' or self._graph.vs[e[0]][HC.FULLTYPE] == type) :
                    pLabels.add(gi2pl[e[0]])
        return list(pLabels)



    def _isConnectionType(self,pLabel) :
        if not self._type.startswith('pattern') :
            self.__raise( \
                'isConnectionType() can only be used in pattern conditions/actions')
        elif pLabel == None :
            self.__raise('isConnectionType() requires a valid __pLabel')
        elif pLabel not in self._pl2gi :
            self.__raise( \
                'invalid isConnectionType() __pLabel :: '+str(pLabel)+' (no node '+ \
                'with that __pLabel exists)')

        return self._graph.vs[self._pl2gi[pLabel]][HC.CONNECTOR_TYPE]




    def _session_get(self,key) :
        if key in self._graph.session :
            return self._graph.session[key]



    def _session_put(self,key,val) :
        if not self._type.endswith('Action') :
            self.__raise( \
                'session_put() can only be used in attribute and pattern actions')

        self._graph.session[key] = val
        return val


    def _pauseTransformation(self):
        self._httpReq("PUT", '127.0.0.1:8125', '/execmode?wid='+self._mtwid, {'mode':'pause'})

    def _stopTransformation(self):
        self._httpReq("PUT", '127.0.0.1:8125', '/execmode?wid='+self._mtwid, {'mode':'stop'})

    def _resumeTransformation(self):
        self._httpReq("PUT", '127.0.0.1:8125', '/execmode?wid='+self._mtwid, {'mode':'play'})

    def _httpReq(self,method,host,uri,data) :
        if host == None :
            return utils.httpReq(
                method,
                '127.0.0.1:8124',
                uri+'?wid='+self._aswid,
                data)
        else :
            return utils.httpReq(method,host,uri,data)



    def _print(self,str) :
        print(str)



    def _sys_call(self,args) :
        try :
            return subprocess.call(args)
        except OSError as ex :
            self.__raise('system call crashed on :: '+ex.strerror)



    def _sys_mkdir(self,path) :
        try :
            return os.makedirs('./users/'+self._username+'/'+path)
        except OSError as ex :
            if ex.errno != 17 :
                #ignore directory already exists error
                self.__raise('directory creation failed :: '+ex.strerror)



    def _sys_readf(self,path) :
        f = open('./users/'+self._username+'/'+path,'r')
        contents = f.read()
        f.close()
        return contents



    def _sys_writef(self,path,content,append=True) :
        if append :
            f = open('./users/'+self._username+'/'+path,'a')
        else :
            f = open('./users/'+self._username+'/'+path,'w')
        f.write(content)
        f.close()

