import GraphTool from './GraphTool.js'
import { replaceTemplates } from '../../lib/libaux.js'



class EdgeDefinition{
    constructor(ecoreSync,model){
        this.ecoreSync=ecoreSync
        this.model=model
        this.anchors=this.model.get('anchors').array()        
    }    

    isStrict()
    {
        return this.model.get('isStrict')?true:false
    }

    async getAnchorTypes(vertices)
    {
        let ts=new eoq2.serialization.TextSerializer();
        var self=this
        let anchorTypes=vertices.map((v)=>{ return [] })
        let encodedEObjects=this.ecoreSync.utils.encode(vertices.map( (v) => v.eObject )) 
        for(let i=0;i<this.anchors.length;i++)
        {
            if(this.anchors[i].get('selectionQuery')!=null &&  this.anchors[i].get('selectionQuery')!='')
            {
                let cmd=new eoq2.Cmp()
                encodedEObjects.forEach( (o) => {
                    // cmd.Get(eval('new eoq2.Obj('+o.v+').'+this.anchors[i].get('selectionQuery')))
                    let cmdStr = "#"+o.v + this.anchors[i].get('selectionQuery')
                    let addCmd = ts.deserialize(cmdStr)
                    if ($DEBUG) console.log('Anchor cmdStr:            '+ cmdStr)
                    cmd.Get(addCmd)
                })    

                //remote call due to performance issues of the local query runner
                let selectionResults=await this.ecoreSync.remoteExec(cmd)             
                vertices.forEach((v,idx)=>{
                    if(selectionResults[idx])
                    {
                        anchorTypes[idx].push(self.anchors[i])
                    }
                })                
            }
        }

        return anchorTypes
    }
}


export default class EdgeRoutingTool extends GraphTool{
    constructor(...args){
        super(...args)
        var self=this
        this.isActive=false;
        this.isEdgeTool=true;
        this.elementId=null;
        this.template=null;
        this.edgeItem=null;
        this.anchorCache=[]; //holds the currently known valid targets

        //Initialize edge definitions
        var edgeDefinitions=this.toolDefinition.get('edgeDefinitions').array()
        this.edgeDefinitions=edgeDefinitions.map((def) => {
            return new EdgeDefinition(self.ecoreSync,def)
        })
    }    

    async activate(){
        if(super.activate()) await this.enableConnectables();
    }

    async deactivate(){
        if(super.deactivate()){
            this.anchorCache=[];
            this.disableConnectables();   
        }
    }

    async isAnchor(eObject)
    {
        var anchors=this.toolDefinition.get("anchors").array();        
        for(let i in anchors)
        {                        
            if(eObject.eClass.get("name")==anchors[i].get("className") && eObject.eClass.eContainer.get("nsURI") == anchors[i].get("nsURI"))
            {
                var res=false;
                if(anchors[i].get("isConditional"))
                {
                    let valueSet={};
                    valueSet["ANCHOR"]=this.ecoreSync.rlookup(eObject);  
                    // TODO this probably needs to be converted to text                  
                    let cmd=eval(replaceTemplates(valueSet,'new eoq2.Get(new eoq2.Obj('+this.ecoreSync.rlookup(eObject)+').'+anchors[i].get("condQuery")+')'));
                    res=await this.ecoreSync.exec(cmd);
                }
                else
                {
                    res=true;
                }
                if(res) { return true }                                             
            }               
        }
        return false                 
    }

    async getAnchorType(eObject)
    {
        var anchors=this.toolDefinition.get("anchors").array();        
        for(let i in anchors)
        {                        
            if(eObject.eClass.get("name")==anchors[i].get("className") && eObject.eClass.eContainer.get("nsURI") == anchors[i].get("nsURI"))
            {
                var res=false;
                if(anchors[i].get("isConditional"))
                {
                    // TODO this probably needs to be converted to text
                    let cmd=eval('new eoq2.Get(new eoq2.Obj('+this.ecoreSync.rlookup(eObject)+').'+anchors[i].get("condQuery")+')');
                    res=await this.ecoreSync.exec(cmd);
                }
                else
                {
                    res=true;
                }
                if(res) { return anchors[i] }                                             
            }               
        }
        return null   
    }

    async filterConnectables(vertices,cache=false){
        var self=this
        var selection=vertices.map((v) => { return false })
        
        if(!this.edgeDefinitions.length)
        {
           console.error('Edge definitions missing')
        }
        else
        {
        
            for(let j=0;j<this.edgeDefinitions.length;j++)
            {
                //Get All Anchor Types of the Edge Definition for all vertices
                let anchorTypes=await this.edgeDefinitions[j].getAnchorTypes(vertices)

                
                for(let i=0;i<vertices.length;i++)
                {                      
                    if(anchorTypes[i].length)
                    {                  
                        selection[i]=true
                        if(cache){
                            anchorTypes[i].forEach(anchorType => {
                                self.anchorCache.push({eObject:vertices[i].eObject,edgeDefinition:this.edgeDefinitions[j],anchorType:anchorType}); 
                            })
                        }
                    }

          
                }
            }
    
        }       

        return selection
    }

    async enableConnectables(){
        var self=this
        this.anchorCache=[];
        this.palette.graphController.setConnectable(async (vertices) => { return self.filterConnectables(vertices,true) })
    }

    disableConnectables(){
        var self=this
        this.palette.graphController.setNotConnectable(async (vertices) => { return self.filterConnectables(vertices,false) });
        this.anchorCache=[];
    }

    async init(){   
        if(!this.toolDefinition) { throw 'tool initialization failed, because no definition was provided' }
        let template=await this.initTemplate()
        if(template)
        {
        this.edgeItem=ecoreSync.clone(this.template)
        await self.ecoreSync.utils.isEClassInitialized(this.edgeItem.eClass)
        }
    }

    getEdgeSpan(source,target)
    {
        var edgeSpan={valid:false, edgeDefinition:null, source:null,target:null,inverted:false};       

        //Collect possible anchors
        var allSourceAnchors=this.anchorCache.filter(function(e){
            return (e.eObject==source || (!e.edgeDefinition.isStrict() && e.eObject==target)) && (e.anchorType.get('type')=='SOURCE' || e.anchorType.get('type')=='BOTH' || e.anchorType.get('type')==null)
        });

        var allTargetAnchors=this.anchorCache.filter(function(e){
            return (e.eObject==target || (!e.edgeDefinition.isStrict() && e.eObject==source)) && (e.anchorType.get('type')=='TARGET' || e.anchorType.get('type')=='BOTH' || e.anchorType.get('type')==null)           
        });
 
  

        for(let sourceAnchor of allSourceAnchors)
        {
            for(let targetAnchor of allTargetAnchors)
            {
                if(sourceAnchor.edgeDefinition == targetAnchor.edgeDefinition)
                {
                    //Match found
                    edgeSpan.edgeDefinition=sourceAnchor.edgeDefinition
                    edgeSpan.source=sourceAnchor
                    edgeSpan.target=targetAnchor
                    
                    //Inversion check
                    if(sourceAnchor.eObject == source && targetAnchor.eObject == target)
                    {
                        edgeSpan.valid=true
                        edgeSpan.inverted=false
                    }
                    else if(!edgeSpan.edgeDefinition.isStrict() && sourceAnchor.eObject == target && targetAnchor.eObject == source)
                    {
                        edgeSpan.valid=true
                        edgeSpan.inverted=true
                    } 

                    break
                }
            }
        }

        return edgeSpan
    }


    canCreate(source,target)
    {
        var edgeSpan=this.getEdgeSpan(source,target)
        return edgeSpan.valid
        // sourceAnchor=sourceAnchor ? sourceAnchor.anchor : null
        // targetAnchor=targetAnchor ? targetAnchor.anchor : null

        // //Sanity check
        // if(sourceAnchor && !sourceAnchor.get('type')){
        //     console.warn('source Anchor type undefined, defaulted to BOTH')
        //     sourceAnchor.set('type','BOTH')
        // }

        // if(targetAnchor && !targetAnchor.get('type')){
        //     console.warn('target Anchor type undefined, defaulted to BOTH')
        //     targetAnchor.set('type','BOTH')
        // }

        // if(sourceAnchor && targetAnchor)
        // {
        //     //strict cases where only source->target
        //     let strictCases={
        //         "CASE_SOURCE_TO_TARGET": sourceAnchor.get('type')=='SOURCE' && targetAnchor.get('type')=='TARGET',
        //         "CASE_SOURCE_TO_BOTH": sourceAnchor.get('type')=='SOURCE' && targetAnchor.get('type')=='BOTH',
        //         "CASE_BOTH_TO_BOTH": sourceAnchor.get('type')=='BOTH' && targetAnchor.get('type')=='BOTH',
        //         "CASE_BOTH_TO_TARGET": sourceAnchor.get('type')=='BOTH' && targetAnchor.get('type')=='TARGET',
        //     }

        //     //non-strict cases where target->source is allowed
        //     let nonStrictCases={
        //         "CASE_TARGET_TO_SOURCE": sourceAnchor.get('type')=='TARGET' && targetAnchor.get('type')=='SOURCE',
        //         "CASE_TARGET_TO_BOTH": sourceAnchor.get('type')=='TARGET' && targetAnchor.get('type')=='BOTH',
        //         "CASE_BOTH_TO_SOURCE": sourceAnchor.get('type')=='BOTH' && targetAnchor.get('type')=='SOURCE',
        //     }

        //     let keys=Object.keys(strictCases);            
        //     for (let i = 0; i < keys.length; i++) {
        //         results=results||strictCases[keys[i]]
        //         if(results) break;
        //     }

        //     if(!isStrict && !results)
        //     {
        //         let keys=Object.keys(nonStrictCases);     
        //         for (let i = 0; i < keys.length; i++) {
        //             results=results||nonStrictCases[keys[i]]
        //             if(results) break;
        //         }
        //     }      
        // }

        // return results;
    }

    async getEdgeItem(){
        let edgeItem=this.edgeItem
        this.edgeItem=ecoreSync.clone(this.template)
        return await edgeItem
    }

    getCmd(edgeItem){
        let valueSet={"ROOT":this.ecoreSync.rlookup(this.palette.graphController.eObject),"EDGE_ITEM":this.ecoreSync.rlookup(edgeItem)}      
        return eval(replaceTemplates(valueSet,this.toolDefinition.get("cmd")))
        // TODO this probably needs to be converted to text
    }

    async routeEdge(edgeDefinition,source, target){
        let ts=new eoq2.serialization.TextSerializer();

        let valueSet={"ROOT":this.palette.graphController.eObject,"SOURCE":source,"TARGET":target} 

        if(this.toolDefinition.get("providesTemplate"))
        {
            let edgeItem=await this.getEdgeItem()
            valueSet["EDGEITEM"]=edgeItem
        }
     
        let routingCmd=new eoq2.Cmp();

        let routingCmds=edgeDefinition.model.get("routingCmd").get("cmds").array();

        routingCmds.forEach(function(cmd){
            try{
                let cmdStr = replaceTemplates(valueSet,cmd.get("cmdStr"))
                let addCmd = ts.deserialize(cmdStr)
                routingCmd.a.push(addCmd);
                // eval('routingCmd.'+replaceTemplates(valueSet,cmd.get("cmdStr")));
            }
            catch(e)
            {
                console.error('routing cmd failed: '+e,'cmd:'+cmd.get("cmdStr"));
                throw e
            }
        })
  
       await this.ecoreSync.exec(routingCmd);
    }

    async create(source,target)
    {     

        var self=this;
        let inverted=false
        var edgeSpan=this.getEdgeSpan(source,target)

        //Check if edge span is valid
        if(!edgeSpan.valid || edgeSpan.edgeDefinition.isStrict() && edgeSpan.inverted)
        {
            throw 'edge span is invalid'
        }

        if(!edgeSpan.inverted)
        {
            this.routeEdge(edgeSpan.edgeDefinition,source,target)
        }
        else
        {
            this.routeEdge(edgeSpan.edgeDefinition,target,source)
        }
        
       

       return true
    
     
    
       
        
        // var sourceAnchor=this.anchorCache.find(function(e){
        //     return e.eObject==source
        // });
        // var targetAnchor=this.anchorCache.find(function(e){
        //     return e.eObject==target        
        // });

        // sourceAnchor=sourceAnchor ? sourceAnchor.anchor : null
        // targetAnchor=targetAnchor ? targetAnchor.anchor : null
        
        // if(sourceAnchor && !sourceAnchor.get('type')){
        //     console.warn('source Anchor type undefined, defaulted to BOTH')
        //     sourceAnchor.set('type','BOTH')
        // }

        // if(targetAnchor && !targetAnchor.get('type')){
        //     console.warn('target Anchor type undefined, defaulted to BOTH')
        //     targetAnchor.set('type','BOTH')
        // }                 

        // if(sourceAnchor && targetAnchor)
        // {
        //     //strict cases where only source->target
        //     let strictCases={
        //         "CASE_SOURCE_TO_TARGET": sourceAnchor.get('type')=='SOURCE' && targetAnchor.get('type')=='TARGET',
        //         "CASE_SOURCE_TO_BOTH": sourceAnchor.get('type')=='SOURCE' && targetAnchor.get('type')=='BOTH',
        //         "CASE_BOTH_TO_BOTH": sourceAnchor.get('type')=='BOTH' && targetAnchor.get('type')=='BOTH',
        //         "CASE_BOTH_TO_TARGET": sourceAnchor.get('type')=='BOTH' && targetAnchor.get('type')=='TARGET',
        //     }

        //     //non-strict cases where target->source is allowed
        //     let nonStrictCases={
        //         "CASE_TARGET_TO_SOURCE": sourceAnchor.get('type')=='TARGET' && targetAnchor.get('type')=='SOURCE',
        //         "CASE_TARGET_TO_BOTH": sourceAnchor.get('type')=='TARGET' && targetAnchor.get('type')=='BOTH',
        //         "CASE_BOTH_TO_SOURCE": sourceAnchor.get('type')=='BOTH' && targetAnchor.get('type')=='SOURCE',
        //     }

        //     let keys=Object.keys(strictCases);            
        //     for (let i = 0; i < keys.length; i++) {
        //         if(strictCases[keys[i]])
        //         {
        //             matched=true   
        //             inverted=false               
        //             break;
        //         }
                
        //     }

        //     if(!isStrict && !matched)
        //     {
        //         let keys=Object.keys(nonStrictCases);     
        //         for (let i = 0; i < keys.length; i++) {
        //             if(nonStrictCases[keys[i]])
        //             {
        //                 matched=true
        //                 inverted=true
        //                 break;
        //             }
        //         }
        //     }      
        // }    
           
        // if(matched && source && target)
        // {
        //    if(!inverted){
        //         this.routeEdge(source,target)
        //    }
        //    else
        //    {
        //         this.routeEdge(target,source)
        //    }
           
        // }
        // else
        // {
        //     throw 'edge routing tool: no suitable match found'
        // }
    }
}