/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.jena.sparql.sse.builders;

import java.util.HashMap ;
import java.util.Map ;

import org.apache.jena.graph.Node ;
import org.apache.jena.sparql.core.TriplePath ;
import org.apache.jena.sparql.path.* ;
import org.apache.jena.sparql.sse.Item ;
import org.apache.jena.sparql.sse.ItemList ;
import org.apache.jena.sparql.sse.Tags ;

public class BuilderPath
{
    public static Path buildPath(Item item)
    {
        BuilderPath bob = new BuilderPath() ;
        return bob.build(item) ;
    }
    
    public static TriplePath buildTriplePath(ItemList list)
    {
        if ( list.size() != 3 && list.size() != 4 )
            BuilderLib.broken(list, "Not a triple path", list) ;
        
        if ( list.size() == 4 )
        {
            if ( ! list.get(0).isSymbol(Tags.tagTriplePath) )
                BuilderLib.broken(list, "Not a triple path") ;
            list = list.cdr() ;
        }
        
        Node s = BuilderNode.buildNode(list.get(0)) ;
        Path p = BuilderPath.buildPath(list.get(1)) ;
        Node o = BuilderNode.buildNode(list.get(2)) ;
        return new TriplePath(s, p, o) ; 
    }

    protected Map<String, Build> dispatch = new HashMap<>() ;
    
    private BuilderPath()
    {
        dispatch.put(Tags.tagPath,    buildPath) ;
        dispatch.put(Tags.tagPathSeq, buildSeq) ;
        dispatch.put(Tags.tagPathAlt, buildAlt) ;
        dispatch.put(Tags.tagPathMod, buildMod) ;
        
        dispatch.put(Tags.tagPathFixedLength,   buildFixedLength) ;
        dispatch.put(Tags.tagPathDistinct,      buildDistinct) ;
        dispatch.put(Tags.tagPathMulti,         buildMulti) ;
        dispatch.put(Tags.tagPathShortest,      buildShortest) ;
        dispatch.put(Tags.tagPathZeroOrOne,     buildZeroOrOne) ;
        dispatch.put(Tags.tagPathZeroOrMore1,   buildZeroOrMore1) ;
        dispatch.put(Tags.tagPathZeroOrMoreN,   buildZeroOrMoreN) ;
        dispatch.put(Tags.tagPathOneOrMore1,    buildOneOrMore1) ;
        dispatch.put(Tags.tagPathOneOrMoreN,    buildOneOrMoreN) ;
        
        dispatch.put(Tags.tagPathReverse,  buildReverse) ;
        dispatch.put(Tags.tagPathRev,      buildRev) ;
        dispatch.put(Tags.tagPathLink,     buildLink) ;   // Completeness.
        dispatch.put(Tags.tagPathNotOneOf, buildNotOneOf) ;
    }
    
    private Path build(Item item)
    {
        if (item.isNode() )
        {
            if ( item.getNode().isURI() )
                return new P_Link(item.getNode()) ;
            BuilderLib.broken(item, "Attempt to build path from a plain node") ;
        }
        
        if (item.isSymbol() )
            BuilderLib.broken(item, "Attempt to build path from a bare symbol") ;
        return build(item.getList()) ;
    }
    
    private Path build(ItemList list)
    {
        Item head = list.get(0) ;
        if ( !head.isSymbol() )
            return build(head) ;
        String tag = head.getSymbol() ;
        Build bob = findBuild(tag) ;
        if ( bob != null )
            return bob.make(list) ;
        else
            BuilderLib.broken(head, "Unrecognized path operation: "+tag) ;
            
        return null ;
    }
    
    static interface Build { Path make(ItemList list) ; }
    
    protected Build findBuild(String str)
    {
        for ( String key : dispatch.keySet() )
        {
            if ( str.equalsIgnoreCase( key ) )
            {
                return dispatch.get( key );
            }
        }
        return null ;
    }
    
    protected Path build(ItemList list, int idx)
    {
        return build(list.get(idx)) ;
    }
    
    final protected Build buildPath = new Build()
    {
        @Override
        public Path make(ItemList list)
        {
            BuilderLib.checkLength(2, list, "path: Exactly one element") ;
            Item item = list.get(1) ;
            return build(item) ;
        }
    } ;
    
    final protected Build buildNotOneOf = new Build()
    {
        @Override
        public Path make(ItemList list)
        {
            BuilderLib.checkLengthAtLeast(1, list, "path: negative property set: must be at least one element") ;
            P_NegPropSet pNegClass = new P_NegPropSet() ;
            for ( int i = 1 ; i < list.size() ; i++ )
            {
                Item item = list.get(i) ;
                // Node or reverse?
                Path p = build(item) ;
                if ( ! ( p instanceof P_Path0 ) )
                    BuilderLib.broken(item, "Not a property or reverse property") ;
                pNegClass.add((P_Path0)p) ;
            }
            return pNegClass ;
        }
    } ;
    
    final protected Build buildSeq = new Build()
    {
        @Override
        public Path make(ItemList list)
        {
            BuilderLib.checkLength(3, 3, list, "path seq: wanted 2 arguments") ;
            Path left = build(list, 1) ;
            Path right  = build(list, 2) ;
            return new P_Seq(left, right) ;
        }
    };

    final protected Build buildAlt = new Build()
    {
        @Override
        public Path make(ItemList list)
        {
            BuilderLib.checkLength(3, 3, list, "path alt: wanted 2 arguments") ;
            Path left = build(list, 1) ;
            Path right  = build(list, 2) ;
            return new P_Alt(left, right) ;
        }

    };

    final protected Build buildMod = new Build()
    {
        @Override
        public Path make(ItemList list)
        {
            BuilderLib.checkLength(4, 4, list, "path mod: wanted 3 arguments") ;
            
            long min = modInt(list.get(1)) ;
            long max = modInt(list.get(2)) ;
            Path path  = build(list, 3) ;
            return new P_Mod(path, min, max) ;
        }
    };

    static long modInt(Item item)
    {
        if ( "_".equals(item.getSymbol()) ) return P_Mod.UNSET ;
        return BuilderNode.buildLong(item) ;
    }

    final protected Build buildFixedLength = new Build()
    {
        @Override
        public Path make(ItemList list)
        {
            BuilderLib.checkLength(3, 3, list, "path fixed repeat: wanted 2 arguments") ;
            long count = modInt(list.get(1)) ;
            Path path  = build(list, 2) ;
            return new P_FixedLength(path, count) ;
        }
    } ;

    final protected Build buildDistinct = new Build()
    {
        @Override
        public Path make(ItemList list)
        {
            BuilderLib.checkLength(2, 2, list, "path distinct: wanted 1 argument") ;
            Path path  = build(list, 1) ;
            return new P_Distinct(path) ;
        }
    } ;

    final protected Build buildMulti = new Build()
    {
        @Override
        public Path make(ItemList list)
        {
            BuilderLib.checkLength(2, 2, list, "path multi : wanted 1 argument") ;
            Path path  = build(list, 1) ;
            return new P_Multi(path) ;
        }
    } ;

    final protected Build buildShortest = new Build()
    {
        @Override
        public Path make(ItemList list)
        {
            BuilderLib.checkLength(2, 2, list, "path shortest : wanted 1 argument") ;
            Path path  = build(list, 1) ;
            return new P_Shortest(path) ;
        }
    } ;

    final protected Build buildZeroOrMore1 = new Build()
    {
        @Override
        public Path make(ItemList list)
        {
            BuilderLib.checkLength(2, 2, list, "path ZeroOrMore1: wanted 1 argument") ;
            Path path  = build(list, 1) ;
            return new P_ZeroOrMore1(path) ;
        }
    } ;
    
    final protected Build buildZeroOrMoreN = new Build()
    {
        @Override
        public Path make(ItemList list)
        {
            BuilderLib.checkLength(2, 2, list, "path ZeroOrMoreN: wanted 1 argument") ;
            Path path  = build(list, 1) ;
            return new P_ZeroOrMoreN(path) ;
        }
    } ;
    

    final protected Build buildZeroOrOne = new Build()
    {
        @Override
        public Path make(ItemList list)
        {
            BuilderLib.checkLength(2, 2, list, "path ZeroOrOne: wanted 1 argument") ;
            Path path  = build(list, 1) ;
            return new P_ZeroOrOne(path) ;
        }
    } ;
    
    final protected Build buildOneOrMore1 = new Build()
    {
        @Override
        public Path make(ItemList list)
        {
            BuilderLib.checkLength(2, 2, list, "path OneOrMore: wanted 1 argument") ;
            Path path  = build(list, 1) ;
            return new P_OneOrMore1(path) ;
        }
    } ;
    
    final protected Build buildOneOrMoreN = new Build()
    {
        @Override
        public Path make(ItemList list)
        {
            BuilderLib.checkLength(2, 2, list, "path OneOrMore: wanted 1 argument") ;
            Path path  = build(list, 1) ;
            return new P_OneOrMoreN(path) ;
        }
    } ;
    
    final protected Build buildReverse = new Build()
    {
        @Override
        public Path make(ItemList list)
        {
            BuilderLib.checkLength(2, 2, list, "path reverse: wanted 1 argument") ;
            Path path = build(list, 1) ;
            return new P_Inverse(path) ;
        }
    };
    
    final protected Build buildLink = new Build()
    {
        @Override
        public Path make(ItemList list)
        {
            BuilderLib.checkLength(2, 2, list, "path link: wanted 1 argument") ;
            return new P_Link(list.get(1).getNode()) ;
        }
    };

    final protected Build buildRev = new Build()
    {
        @Override
        public Path make(ItemList list)
        {
            BuilderLib.checkLength(2, 2, list, "path reverse link: wanted 1 argument") ;
            return new P_ReverseLink(list.get(1).getNode()) ;
        }
    };

}
