'''
 2019 Bjoern Annighoefer
'''

from ..value import VAL, PRM, BOL, U32, U64, STR, I64, LST, NON, EncVal
from ..query import Obj
from ..error import EOQ_ERROR_RUNTIME
#type checking
from typing import Dict, List, Any, Type, Union, Tuple
from abc import ABC

CMD_SEP = '\n'
CMD_ARG_SEP = ' '

class CMD_TYPES:
    #data related commmands
    GET = 'GET' #get cmd
    UPD = 'UPD' #update cmd
    SET = 'SET' #set cmd value #depricated 
    ADD = 'ADD' #add cmd value #depricated
    REM = 'REM' #remove cmd value #depricated
    MOV = 'MOV' #move cmd cmd #depricated
    DEL = 'DEL' #delete cmd
    CLO = 'CLO' #clone source target mode
    CRT = 'CRT' #create 
    QRF = 'QRF' #querify
    
    #comparison and merge
    CPR = 'CPR' #compare/diff
    MRG = 'MRG' #merge 
    
    #maintenance related commands
    HEL = 'HEL' #hello
    GBY = 'GBY' #goodbye
    SES = 'SES' #session
    STS = 'STS' #status
    CHG = 'CHG' #changes
    OBS = 'OBS' #observe
    UBS = 'UBS' #unobserve
    MSG = 'MSG' #send message to observers
    
    # extensions
    SCC = 'SCC' #set custom command
    CUS = 'CUS' #invoke custom command. Only used internally.
    
    # placeholders (NOT IMPLEMENTED)
    UDO = 'UDO' #undo
    RDO = 'RDO' #redo
    LCK = 'LCK' #lock
    ULK = 'ULK' #unlock

    # Verification related commands
    VER = 'VER' #verify model element against constraint 
    GAC = 'GAC' #get all applicable constraints for target
    
       
    #Result
    RES = 'RES' #result
    ERR = 'ERR' #error
    
    #Event
    EVT = 'EVT' #event
    
    #Compound commands
    CMP = 'CMP' #compound
    

class DEL_MODES:
    BAS = 'BAS' #base: delete the element itself. Does only work for elements not contained and not referenced
    AUT = 'AUT' #auto: delete the element itself. All containments and references are removed beforehand
    FUL = 'FUL' #all: delete element and all children recursively
   
    
class CLO_MODES:
    MIN = 'MIN' #minimum: clone only concept and create args
    BAS = 'BAS' #base: clone concept and all updateable all properties
    FLT = 'FLT' #removed: #flat: create the identical copy of the concept and all direct elements in the clone path
    FUL = 'FUL' #full: clone all elements in the clone path recursively

class MRG_MODES:
    STD = 'STD' #standard mode without additional information or output
    LST = 'LST' #list: creates a list with all commands
    LOG = 'LOG' #log: LST + creates a log file (info.log) with detailed information on every object
    DET = 'DET' #detailed: LOG + creates a file with all objects and their #1234 number
    
    
class EVT_TYPES:
    ELM = 'ELM' # element (this is not a real event, but used to make objects observed)
    CRT = 'CRT' # create
    UPD = 'UPD' # update
    DEL = 'DEL' # delete
    MSG = 'MSG' # message
    WAT = 'WAT' # watch event (not implemented)
    VEC = 'VEC' # verification change
    
class EVT_KEYS:
    ALL = '*' # watch all events
    
### COMMAND BASE CLASS ###    
    
class Cmd(ABC):
    def __init__(self,t:str, mute:bool=False, resName:str=None):
        self.cmd:str                = t
        self.a:List[Union[Cmd,VAL]] = []
        self.r:str                  = resName #result name
        self.m:bool                 = mute #mute the output
        
    def __eq__(self,other:Any)->bool:
        if(isinstance(other,Cmd)):
            if(self.cmd!=other.cmd or \
               self.m != other.m or \
               self.r != other.r): 
                return False
            n = len(self.a)
            m = len(other.a)
            if(n!=m):
                return False
            for i in range(n):
                if(self.a[i]!=other.a[i]):
                    return False
            return True #return true if all arguments are the same
        else:
            False 
        
    def __repr__(self)->str:
        cmdStr = ("-" if self.m else "")+str(self.cmd)
        if(len(self.a) > 0):
            cmdStr += CMD_ARG_SEP + CMD_ARG_SEP.join([str(arg) for arg in self.a])
        if(None!=self.r):
            cmdStr += " -> $"+self.r
        return cmdStr
    
    
### COMMAND REGISTRY ###

class CmdInfo:
    def __init__(self,cmdId:str, minArgs:int, maxArgs:int, description:str, clazz:Type[Cmd], unpackArgs:bool=True, optDefVals:List[Any]=[]):
        self.cmdId = cmdId
        self.minArgs = minArgs
        self.maxArgs = maxArgs
        self.description = description
        self.clazz = clazz
        self.unpackArgs = unpackArgs
        self.optArgs:int = maxArgs-minArgs
        self.optDefVals = optDefVals
        
        
EOQ_COMMAND_REGISTRY:Dict[str,CmdInfo] = {}
    

def RegisterCmdType(cmdId:str, minArgs:int, maxArgs:int, description:str, clazz:Type[Cmd], unpackArgs:bool=True, optDefVals:List[Any]=[]):
    EOQ_COMMAND_REGISTRY[cmdId] = CmdInfo(cmdId,minArgs,maxArgs,description,clazz,unpackArgs,optDefVals)
    
def CmdFactory(cmdId:str, args:List[Any], mute:bool=False, resName:str=None):
    try: 
        cmdInfo = EOQ_COMMAND_REGISTRY[cmdId]
        nArgs = len(args)
        if(nArgs < cmdInfo.minArgs or (cmdInfo.maxArgs > 0 and cmdInfo.maxArgs < nArgs)):
            raise EOQ_ERROR_RUNTIME('%s requires between %d and %d arguments, but got %d'%(cmdId,cmdInfo.minArgs,cmdInfo.maxArgs,nArgs))
        if(cmdInfo.unpackArgs):
            return cmdInfo.clazz(*args,mute=mute,resName=resName) #create a new instance
        else:
            return cmdInfo.clazz(args,mute=mute,resName=resName) #create a new instance
    except KeyError:
        return Cus(cmdId,args,mute=mute,resName=resName)
    
def GetCmdInfo(cmdId:str)->CmdInfo:
    if(cmdId in EOQ_COMMAND_REGISTRY):
        return EOQ_COMMAND_REGISTRY[cmdId]
    else:
        return None

class PrmCmd(Cmd,ABC): 
    '''Primitive command a base class for primitive commands, which fixes the argument type 
    '''
    def __init__(self,t:str, args:List[Any], mute:bool=False, resName:str=None):
        super().__init__(t, mute, resName)
        self.a:List[VAL] = [EncVal(a) for a in args]
    
### BASIC COMMANDS ###
        
class Get(PrmCmd):
    def __init__(self, target:Any, mute:bool=False, resName:str=None):
        super().__init__(CMD_TYPES.GET, [target],mute,resName)     
RegisterCmdType(CMD_TYPES.GET,1,1,'Retrieve values',Get,True)
        
class Upd(PrmCmd):
    def __init__(self, target:Any, feature:Any, value:Any, position:Any=0,mute:bool=False, resName:str=None):
        super().__init__(CMD_TYPES.UPD, [target,feature,value,I64(position)],mute,resName)
RegisterCmdType(CMD_TYPES.UPD,3,4,'Change, add or delete the value of a feature.',Upd,True,[-10]) #-10 = hack to prevent argument stripping 
        
class Del(PrmCmd):
    def __init__(self, target:Any, mode:Any=DEL_MODES.BAS,mute:bool=False, resName:str=None):
        super().__init__(CMD_TYPES.DEL, [target,mode],mute,resName)
RegisterCmdType(CMD_TYPES.DEL,1,2,'Delete an element.',Del,True,[DEL_MODES.BAS]) 
        
class Set(PrmCmd):
    def __init__(self, target:Any, feature:Any, value:Any, mute:bool=False, resName:str=None):
        super().__init__(CMD_TYPES.SET, [target,feature,value],mute,resName)
RegisterCmdType(CMD_TYPES.SET,3,3,'Set a feature.',Set,True) 
        
class Add(PrmCmd):
    def __init__(self, target:Any, feature:Any, value:Any, mute:bool=False, resName:str=None):
        super().__init__(CMD_TYPES.ADD, [target,feature,value],mute,resName)
RegisterCmdType(CMD_TYPES.ADD,3,3,'Add values to a multi-value feature.',Add,True) 
        
class Rem(PrmCmd):
    def __init__(self, target:Any, feature:Any, value:Any, mute:bool=False, resName:str=None):
        super().__init__(CMD_TYPES.REM, [target,feature,value],mute,resName)
RegisterCmdType(CMD_TYPES.REM,3,3,'Remove a value from a feature.',Rem,True) 
        
class Mov(PrmCmd):
    def __init__(self, target:Any, newIndex:Any, mute:bool=False, resName:str=None):
        super().__init__(CMD_TYPES.MOV, [target,newIndex],mute,resName)
RegisterCmdType(CMD_TYPES.MOV,2,2,'Remove a value from a feature.',Mov,True) 
        
class Clo(PrmCmd):
    def __init__(self, target:Any, mode:Any=STR(CLO_MODES.MIN), createArgOverrides:Any=LST([]), mute:bool=False, resName:str=None):
        super().__init__(CMD_TYPES.CLO,[target,mode,createArgOverrides],mute,resName)
RegisterCmdType(CMD_TYPES.CLO,1,3,'Clone an element.',Clo,True,[CLO_MODES.MIN,[]]) 
          
class Crt(PrmCmd):
    def __init__(self, classId:Any, n:Any=U32(1), createArgs:Any=LST([]), target:Any=NON(), recoveryArgs:Any=LST([]),mute:bool=False, resName:str=None):
        super().__init__(CMD_TYPES.CRT,[classId,n,createArgs,target,recoveryArgs],mute,resName)
RegisterCmdType(CMD_TYPES.CRT,2,5,'Create element by class ID (name or Obj).',Crt,True,[[],None,[]]) 
        
class Cpr(PrmCmd):
    def __init__(self, dst:Any, src:Any, mode:Any, mute:bool=False, resName:str=None):
        super().__init__(CMD_TYPES.CPR,[dst,src,mode],mute,resName)
RegisterCmdType(CMD_TYPES.CPR,3,3,'Compare two element and retrieve the difference.',Cpr,True) 
        
class Mrg(PrmCmd):
    def __init__(self, dst:Any, src:Any, mode:Any, mute:bool=False, resName:str=None):
        super().__init__(CMD_TYPES.MRG,[dst,src,mode],mute,resName)
RegisterCmdType(CMD_TYPES.MRG,3,3,'Merge all changes of new into old.',Mrg,True) 
        
### DOMAIN RELATED COMMANDS
        
class Hel(PrmCmd):
    def __init__(self, user:Any, password:Any, mute:bool=False, resName:str=None):
        super().__init__(CMD_TYPES.HEL, [user, password],mute,resName)
RegisterCmdType(CMD_TYPES.HEL,2,2,'Log into the domain. The login persists for the actual session id.',Hel,True) 
        
class Ses(PrmCmd):
    def __init__(self, sessionId:Any, mute:bool=False, resName:str=None):
        super().__init__(CMD_TYPES.SES, [sessionId],mute,resName)
RegisterCmdType(CMD_TYPES.SES,1,1,'Set the session id for the current transaction. Only makes sense in a compound command.',Ses,True) 
        
class Gby(PrmCmd):
    def __init__(self, mute:bool=False, resName:str=None):
        super().__init__(CMD_TYPES.GBY, [],mute,resName)
RegisterCmdType(CMD_TYPES.GBY,0,0,'Log off the given session.',Gby,True) 
        
class Sts(PrmCmd):
    def __init__(self, mute:bool=False, resName:str=None):
        super().__init__(CMD_TYPES.STS, [],mute,resName)
RegisterCmdType(CMD_TYPES.STS,0,0,'Status returns the latest change ID.',Sts,True) 
        
class Chg(PrmCmd):
    def __init__(self, latestChangeId:Any, n:Any, mute:bool=False, resName:str=None):
        super().__init__(CMD_TYPES.CHG,[latestChangeId,n],mute,resName)
RegisterCmdType(CMD_TYPES.CHG,2,2,'Retrieve change records.',Chg,True) 
        
class Obs(PrmCmd):
    def __init__(self, eventType:Any, eventKey:Any=None, mute:bool=False, resName:str=None):
        super().__init__(CMD_TYPES.OBS, [eventType,eventKey],mute,resName)
RegisterCmdType(CMD_TYPES.OBS,1,2,'Start observing events for the current session.',Obs,True,[None]) 
        
class Ubs(PrmCmd):
    def __init__(self, eventType:Any, eventKey:Any=None, mute:bool=False, resName:str=None):
        super().__init__(CMD_TYPES.UBS, [eventType,eventKey],mute,resName)
RegisterCmdType(CMD_TYPES.UBS,1,2,'Stop observing events for the current session.',Ubs,True,[None]) 

class Msg(PrmCmd):
    def __init__(self, msgKey:Any, msg:Any, mute:bool=True,resName : str = None):
        super().__init__(CMD_TYPES.MSG, [msgKey,msg],mute,resName)
RegisterCmdType(CMD_TYPES.MSG,2,2,'Sending a message to observers.',Msg,True) 
        
# class Lck(Cmd):
#     def __init__(self,timeout : float,mute:bool=False, resName:str=None):
#         super().__init__(CMD_TYPES.LCK, [timeout],mute,resName)
# RegisterCmdType(CMD_TYPES.LCK,1,1,'Lock the domain to the active session.',Lck,True) 
#         
# class Ulk(Cmd):
#     def __init__(self,mute:bool=False, resName:str=None):
#         super().__init__(CMD_TYPES.LCK, [],mute,resName)
# RegisterCmdType(CMD_TYPES.ULK,0,0,'Unlock release the domain lock.',Ulk,True) 
        
        
### CONSTRAINT RELATED COMMANDS ###

class Ver(PrmCmd):
    def __init__(self, target:Any, mute: bool=False,resName : str = None):
        super().__init__(CMD_TYPES.VER, [target],mute,resName)
RegisterCmdType(CMD_TYPES.VER,1,1,'Verify',Ver,True)

class Gac(PrmCmd):
    def __init__(self, target:Any, mute: bool=False,resName : str = None):
        super().__init__(CMD_TYPES.GAC, [target],mute,resName)
RegisterCmdType(CMD_TYPES.GAC,1,1,'Get all constraints',Gac,True)
        
        
### RESULT COMMAND ###

class Res(PrmCmd):
    def __init__(self, tid:Any, cid:Any, val:Any, resNames:List[str]=[],resNameIndizies:List[int]=[],mute: bool=False,resName : str = None):
        super().__init__(CMD_TYPES.RES, [U64(tid),U64(cid),val,resNames,resNameIndizies],mute,resName)
RegisterCmdType(CMD_TYPES.RES,3,5,'Result',Res,True,[[],[]]) 
        
class Err(PrmCmd):
    def __init__(self, code:int,msg:Any='', debug:Any='',mute: bool=False, resName:str=None):
        super().__init__(CMD_TYPES.ERR, [U32(code),STR(msg),STR(debug)],mute,resName)
RegisterCmdType(CMD_TYPES.ERR,1,3,'Error result',Err,True,['','']) 
        
        
### EVENT COMMANDS ###

class Evt(PrmCmd):
    def __init__(self, eventType:STR, data:LST, mute:bool=False, resName:str=None):
        super().__init__(CMD_TYPES.EVT, [eventType, data])
RegisterCmdType(CMD_TYPES.EVT,2,2,'Event',Evt,True)
        
class UpdEvt(Evt):
    def __init__(self, cid:U64, target:Obj, feature:STR, value:PRM, position:I64, user:STR):
        super().__init__(STR(EVT_TYPES.UPD),LST([cid,target,feature,value,position,user]))
        
class CrtEvt(Evt):
    def __init__(self, cid:U64, target:Obj, classId:Tuple[STR,Obj], createArgs:LST, user:STR):
        super().__init__(STR(EVT_TYPES.CRT),LST([cid,target,classId,createArgs,user]))
        
class DelEvt(Evt):
    def __init__(self, cid:U64, target:Obj, user:STR):
        super().__init__(STR(EVT_TYPES.DEL),LST([cid,target,user]))
        
class MsgEvt(Evt):
    def __init__(self, msgKey:STR, msg:STR):
        super().__init__(STR(EVT_TYPES.MSG),LST([msgKey, msg]))
        
class WatEvt(Evt):
    def __init__(self, wid:U32, result:VAL, error:STR):
        super().__init__(STR(EVT_TYPES.WAT),LST([wid, result, error]))
        
class VecEvt(Evt):
    def __init__(self, target:Obj,  constraint:Obj, isValid:BOL,  error:STR):
        super().__init__(STR(EVT_TYPES.VEC),LST([target,constraint,isValid,error]))
        
        
### CUSTOM COMMAND ###

class Scc(PrmCmd):
    def __init__(self, cmdId:Any, cmdStr:Any, mute:bool=False, resName:str = None):
        super().__init__(CMD_TYPES.SCC, [cmdId,cmdStr],mute,resName)
RegisterCmdType(CMD_TYPES.SCC,2,2,'Set custom command',Scc,True) 


class Cus(PrmCmd):
    def __init__(self, cmdId:Any, args:List[any], mute:bool=False, resName:str=None):
        super().__init__(cmdId, args, mute, resName)
#RegisterCmdType(CMD_TYPES.CUS,1,1,'Custom command',Cus,True) 
    
        

### COMPOUND COMMAND ###

class Cmp(Cmd):
    def __init__(self, cmds:List[Cmd]=None ,mute: bool=False, resName:str=None):
        super().__init__(CMD_TYPES.CMP,mute,resName)
        self.a:Cmd = []
        if(None != cmds):
            self.a = [c for c in cmds if isinstance(c,Cmd)]
        
    def Get(self, target:Any, mute:bool=False, resName:str=None):
        self.a.append(Get(target,mute,resName))
        return self
    
    def Upd(self, target:Any, feature:Any, value:Any, position:Any=0, mute:bool=False, resName:str=None):
        self.a.append(Upd(target,feature,value,position,mute,resName))
        return self
    
    def Del(self, target:Any, mode:Any=DEL_MODES.BAS, mute:bool=False, resName:str=None):
        self.a.append(Del(target,mode,mute,resName))
        return self
    
    def Set(self, target:Any, feature:Any, value:Any, mute:bool=False, resName:str=None):
        self.a.append(Set(target,feature,value,mute,resName))
        return self
    
    def Add(self, target:Any, feature:Any, value:Any, mute:bool=False, resName:str=None):
        self.a.append(Add(target,feature,value,mute,resName))
        return self
    
    def Rem(self, target:Any, feature:Any, value:Any, mute:bool=False, resName:str=None):
        self.a.append(Rem(target,feature,value,mute,resName))
        return self
    
    def Mov(self, target:Any, newIndex:Any, mute:bool=False, resName:str=None):
        self.a.append(Mov(target,newIndex,mute,resName))
        return self
    
    def Clo(self, target:Any, mode:Any=STR(CLO_MODES.MIN), createArgOverrides:Any=LST([]), mute:bool=False, resName:str=None):
        self.a.append(Clo(target,mode,createArgOverrides,mute,resName))
        return self
    
    def Crt(self, classId:Any, n:Any=U32(1), createArgs:Any=LST([]), target:Any=NON(), recoveryArgs:Any=LST([]), mute:bool=False, resName:str=None):
        self.a.append(Crt(classId,n,createArgs,target,recoveryArgs,mute,resName))
        return self
    
    def Mrg(self, dst:Any, src:Any, mode:Any, mute:bool=False, resName:str=None):
        self.a.append(Mrg(dst,src,mode,mute,resName))
        return self

    def Cpr(self,dst,src,mode,mute:bool=False, resName:str=None):
        self.a.append(Cpr(dst,src,mode,mute,resName))
        return self
    
    def Hel(self, user:Any, password:Any, mute:bool=False, resName:str=None):
        self.a.append(Hel(user,password,mute,resName))
        return self
    
    def Ses(self, sessionId:Any, mute:bool=False, resName:str=None):
        self.a.append(Ses(sessionId,mute,resName))
        return self
    
    def Gby(self, mute:bool=False, resName:str=None):
        self.a.append(Gby(mute,resName))
        return self
    
    def Sts(self, mute:bool=False, resName:str=None):
        self.a.append(Sts(mute,resName))
        return self
    
    def Chg(self, changeId:Any, n:Any, mute:bool=False, resName:str=None):
        self.a.append(Chg(changeId,n,mute,resName))
        return self
    
    def Obs(self, eventType:Any, eventKey:Any=None, mute:bool=False, resName:str=None):
        self.a.append(Obs(eventType,eventKey,mute,resName))
        return self
    
    def Ubs(self, eventType:Any, eventKey:Any=None, mute:bool=False, resName:str=None):
        self.a.append(Ubs(eventType,eventKey,mute,resName))
        return self

    def Ver(self, target:Any):
        self.a.append(Ver(target))
        return self

    def Gac(self,target:Any):
        self.a.append(Gac(target))
        return self
    
    def Scc(self, cmdId:Any, cmdStr:Any, mute:bool=False, resName:str = None):
        self.a.append(Scc(cmdId,cmdStr,mute,resName))
        return self
    
    def Cus(self, cmdId:Any, args:List[Any], mute:bool=False, resName:str=None):
        self.a.append(Cus(cmdId,args,mute=mute,resName=resName))
        return self
        
    def Append(self, cmd:Cmd):
        '''Appends a command to the compound
        '''
        self.a.append(cmd)
        return self
            
RegisterCmdType(CMD_TYPES.CMP,0,-1,'A compound command composed of a list of subcommands.',Cmp,False) 

### HELPERS ###

def IsReadOnlyCmd(cmd:Cmd)->bool:
    ''' Returns true for commands that do not modify the MDB
    '''
    if(isinstance(cmd,Get)): #Get is the only option for non changing commands
        return True
    elif(isinstance(cmd,Cmp)): #in case of compound commands it depends on the subcommands
        for a in cmd.a:
            if(not IsReadOnlyCmd(a)):
                return False
        return True
    else:
        return False #all other commands are expected to have changes
    



