/*
 * 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 ( item.isDefault() )
            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());
        }
    };
}
