/*
 * 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
 *
 *   https://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.
 *
 *   SPDX-License-Identifier: Apache-2.0
 */

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

import org.apache.jena.atlas.io.IndentedLineBuffer;
import org.apache.jena.atlas.io.IndentedWriter;
import org.apache.jena.graph.Node;
import org.apache.jena.sparql.core.Prologue;
import org.apache.jena.sparql.core.TriplePath;
import org.apache.jena.sparql.path.*;
import org.apache.jena.sparql.serializer.SerializationContext;
import org.apache.jena.sparql.sse.Tags;

/** SSE Writer */
public class WriterPath {
    private static final int NL = SSEWriteLib.NL;
    private static final int NoNL = SSEWriteLib.NoNL;
    private static final int NoSP = SSEWriteLib.NoSP;

    public static void write(Path path, Prologue prologue) {
        output(IndentedWriter.stdout, path, new SerializationContext(prologue));
    }

    public static void output(IndentedWriter out, Path path, SerializationContext naming) {
        WriterPathVisitor w = new WriterPathVisitor(out, naming);
        w.output(path);
        w.out.flush();
    }

    public static void output(IndentedWriter out, TriplePath tp, SerializationContext naming) {
        SSEWriteLib.start(out, Tags.tagTriplePath, NoNL);
        outputPlain(out, tp, naming);
        SSEWriteLib.finish(out, Tags.tagTriplePath);
    }

    public static void outputPlain(IndentedWriter out, TriplePath tp, SerializationContext naming) {
        boolean oneLiner = oneLiner(tp.getPath());
        if ( oneLiner ) {
            WriterNode.output(out, tp.getSubject(), naming);
            out.print(" ");
            WriterPath.output(out, tp.getPath(), naming);
            out.print(" ");
            WriterNode.output(out, tp.getObject(), naming);
        } else {
            nl(out, false);
            WriterNode.output(out, tp.getSubject(), naming);
            nl(out);
            WriterPath.output(out, tp.getPath(), naming);
            nl(out);
            WriterNode.output(out, tp.getObject(), naming);
        }
    }

    private static boolean oneLiner(Path path) {
        return (path instanceof P_Link);
    }

    private static final boolean multiline = false;
    private static final boolean maxBracket = false;

    private static void nl(IndentedWriter out) {
        nl(out, true);
    }

    private static void nl(IndentedWriter out, boolean spaceForNL) {
        if ( multiline )
            out.println();
        else if ( spaceForNL )
            out.print(" ");
    }

    public static String asString(Path path) {
        return asString(path, (SerializationContext)null);
    }

    public static String asString(Path path, SerializationContext sCxt) {
        IndentedLineBuffer buff = new IndentedLineBuffer();
        WriterPathVisitor w = new WriterPathVisitor(buff, sCxt);
        path.visit(w);
        w.out.flush();
        return buff.asString();
    }

    private static class WriterPathVisitor implements PathVisitor {

        private IndentedWriter out;
        private SerializationContext sCxt;

        WriterPathVisitor(IndentedWriter indentedWriter, SerializationContext sCxt) {
            this.out = indentedWriter;
            this.sCxt = sCxt;
        }

        private void output(Path path) {
            path.visit(this);
        }

        private void output(Node node) {
            WriterNode.output(out, node, sCxt);
        }

        @Override
        public void visit(P_Link pathNode) {
            output(pathNode.getNode());
        }

        @Override
        public void visit(P_ReverseLink pathNode) {
            out.print("(");
            out.print(Tags.tagPathRev);
            out.print(" ");
            output(pathNode.getNode());
            out.print(")");
        }

        @Override
        public void visit(P_NegPropSet pathNotOneOf) {
            out.print("(");
            out.print(Tags.tagPathNotOneOf);

            for ( P_Path0 p : pathNotOneOf.getNodes() ) {
                out.print(" ");
                output(p);
            }
            out.print(")");
        }

        @Override
        public void visit(P_Alt pathAlt) {
            visit2(pathAlt, Tags.tagPathAlt);
        }

        @Override
        public void visit(P_Seq pathSeq) {
            visit2(pathSeq, Tags.tagPathSeq);
        }

        private void visit2(P_Path2 path2, String nodeName) {
            out.print("(");
            out.print(nodeName);
            nl(out);
            out.incIndent();
            output(path2.getLeft());
            nl(out);
            output(path2.getRight());
            out.decIndent();
            out.print(")");
        }

        @Override
        public void visit(P_Mod pathMod) {
            out.print("(");
            out.print(Tags.tagPathMod);
            out.print(" ");
            out.print(modInt(pathMod.getMin()));
            out.print(" ");
            out.print(modInt(pathMod.getMax()));
            writeOneLiner(pathMod.getSubPath());
            out.print(")");
        }

        private static String modInt(long value) {
            if ( value == P_Mod.INF )
                return "*";
            if ( value == P_Mod.UNSET )
                return "_";
            return Long.toString(value);
        }

        @Override
        public void visit(P_FixedLength path) {
            out.print("(");
            out.print(Tags.tagPathFixedLength);
            out.print(" ");

            out.print(modInt(path.getCount()));
            writeOneLiner(path.getSubPath());
            out.print(")");
        }

        @Override
        public void visit(P_Distinct pathDistinct) {
            writePath(Tags.tagPathDistinct, pathDistinct.getSubPath());
        }

        @Override
        public void visit(P_Multi pathMulti) {
            writePath(Tags.tagPathMulti, pathMulti.getSubPath());
        }

        @Override
        public void visit(P_Shortest path) {
            writePath(Tags.tagPathShortest, path.getSubPath());
        }

        @Override
        public void visit(P_ZeroOrOne path) {
            writePath(Tags.tagPathZeroOrOne, path.getSubPath());
        }

        @Override
        public void visit(P_ZeroOrMore1 path) {
            writePath(Tags.tagPathZeroOrMore1, path.getSubPath());
        }

        @Override
        public void visit(P_ZeroOrMoreN path) {
            writePath(Tags.tagPathZeroOrMoreN, path.getSubPath());
        }

        @Override
        public void visit(P_OneOrMore1 path) {
            writePath(Tags.tagPathOneOrMore1, path.getSubPath());
        }

        @Override
        public void visit(P_OneOrMoreN path) {
            writePath(Tags.tagPathOneOrMoreN, path.getSubPath());
        }

        private void writeOneLiner(Path path) {
            if ( oneLiner(path) )
                out.print(" ");
            else
                nl(out);
            out.incIndent();
            output(path);
            out.decIndent();
        }

        private void writePath(String tag, Path subPath) {
            out.print("(");
            out.print(tag);
            writeOneLiner(subPath);
            out.print(")");
        }

        @Override
        public void visit(P_Inverse reversePath) {
            out.print("(");
            out.print(Tags.tagPathReverse);
            nl(out);

            out.incIndent();
            Path p = reversePath.getSubPath();
            output(p);
            out.decIndent();
            nl(out, false);

            out.print(")");
        }
    }
}
