/*
 * 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.modify.request;

import java.util.Iterator ;

import org.apache.jena.atlas.io.IndentedWriter ;
import org.apache.jena.graph.Node ;
import org.apache.jena.graph.Triple ;
import org.apache.jena.sparql.core.Prologue ;
import org.apache.jena.sparql.core.Quad ;
import org.apache.jena.sparql.modify.request.UpdateDataWriter.UpdateMode ;
import org.apache.jena.sparql.serializer.PrologueSerializer ;
import org.apache.jena.sparql.serializer.SerializationContext ;
import org.apache.jena.sparql.util.NodeToLabelMapBNode ;
import org.apache.jena.update.Update ;
import org.apache.jena.update.UpdateRequest ;

public class UpdateWriter implements UpdateSerializer
{
    protected final IndentedWriter out;
    protected final SerializationContext sCxt;

    protected UpdateDataWriter udw;
    protected boolean firstOp = true;
    protected boolean opened = false;

    /** Create a UpdateWriter for output of a single UpdateRequest.
     *  @param out
     *  @param sCxt SerializationContext - pass null for one that will produce legal output.
     */
    public UpdateWriter(IndentedWriter out, SerializationContext sCxt)
    {
        if (out == null)
            throw new NullPointerException("out") ;

        // To get legal syntax out, the serialization context
        // has to be a bNode mapping that does ??N vars to bNodes
        if (sCxt == null)
            // This is the one used for INSERT and DELETE templates.
            // For the WHERE clause, see UpdateWriterVisitor.prepareElementFormatter
            sCxt = new SerializationContext((Prologue)null, new NodeToLabelMapBNode());

        this.out = out;
        this.sCxt = sCxt;
    }

    /* (non-Javadoc)
     * @see com.hp.hpl.jena.sparql.modify.request.UpdateSerializer#open()
     */
    @Override
    public void open()
    {
        if (null != sCxt)
            prologue();
        opened = true;
    }

    protected void checkOpen()
    {
        if (!opened)
            throw new IllegalStateException("UpdateStreamWriter is not opened.  Call open() first.");
    }

    protected void prologue()
    {
        int row1 = out.getRow() ;
        PrologueSerializer.output(out, sCxt.getPrologue()) ;
        int row2 = out.getRow() ;
        if ( row1 != row2 )
            out.newline() ;
    }

    protected void prepareForDataUpdate(UpdateMode mode)
    {
        if ((null != udw) && !udw.getMode().equals(mode))
        {
            udw.close();
            udw = null;
            firstOp = false;
        }

        if (null == udw)
        {
            if (!firstOp)
            {
                out.println(" ;");
            }
            udw = new UpdateDataWriter(mode, out, sCxt);
            udw.open();
            firstOp = false;
        }
    }

    public void insert(Quad quad)
    {
        insert(quad.getGraph(), quad.asTriple());
    }

    public void insert(Iterator<? extends Quad> it)
    {
        checkOpen();
        prepareForDataUpdate(UpdateMode.INSERT);
        while (it.hasNext())
        {
            udw.send(it.next());
        }
    }

    public void insert(Node graph, Triple triple)
    {
        checkOpen();
        prepareForDataUpdate(UpdateMode.INSERT);
        udw.send(graph, triple);
    }

    public void insert(Node graph, Iterator<? extends Triple> it)
    {
        checkOpen();
        prepareForDataUpdate(UpdateMode.INSERT);
        while (it.hasNext())
        {
            udw.send(graph, it.next());
        }
    }

    public void delete(Quad quad)
    {
        delete(quad.getGraph(), quad.asTriple());
    }

    public void delete(Iterator<? extends Quad> it)
    {
        checkOpen();
        prepareForDataUpdate(UpdateMode.DELETE);
        while (it.hasNext())
        {
            udw.send(it.next());
        }
    }

    public void delete(Node graph, Triple triple)
    {
        checkOpen();
        prepareForDataUpdate(UpdateMode.DELETE);
        udw.send(graph, triple);
    }

    public void delete(Node graph, Iterator<? extends Triple> it)
    {
        checkOpen();
        prepareForDataUpdate(UpdateMode.DELETE);
        while (it.hasNext())
        {
            udw.send(graph, it.next());
        }
    }

    /* (non-Javadoc)
     * @see com.hp.hpl.jena.sparql.modify.request.UpdateSerializer#update(com.hp.hpl.jena.update.Update)
     */
    @Override
    public void update(Update update)
    {
        checkOpen();
        if (null != udw)
        {
            udw.close();
            udw = null;
        }

        if (!firstOp)
        {
            out.println(" ;");
        }
        UpdateVisitor writer = prepareWriterVisitor() ;
        update.visit(writer) ;

        firstOp = false;
    }

    /**
     * Prepares a visitor which is used to visit the actual updates that make up the update request and write them out
     * @return Update visitor
     */
    protected UpdateVisitor prepareWriterVisitor() {
        return new UpdateWriterVisitor(out, sCxt);
    }

    /* (non-Javadoc)
     * @see com.hp.hpl.jena.sparql.modify.request.UpdateSerializer#update(java.lang.Iterable)
     */
    @Override
    public void update(Iterable<? extends Update> updates)
    {
        update(updates.iterator());
    }

    /* (non-Javadoc)
     * @see com.hp.hpl.jena.sparql.modify.request.UpdateSerializer#update(java.util.Iterator)
     */
    @Override
    public void update(Iterator<? extends Update> updateIter)
    {
        while (updateIter.hasNext())
        {
            update(updateIter.next());
        }
    }

    public void flush()
    {
        out.flush();
    }

    /* (non-Javadoc)
     * @see com.hp.hpl.jena.sparql.modify.request.UpdateSerializer#close()
     */
    @Override
    public void close()
    {
        if (opened)
        {
            if (null != udw)
            {
                udw.close();
                udw = null;
            }

            // Update requests always end in newline.
            out.ensureStartOfLine();
            flush();
            opened = false;
        }
    }

    // -- Convenience static methods -----------------------

    public static void output(UpdateRequest request, IndentedWriter out)
    {
        Prologue prologue = request ;
        if ( ! request.explicitlySetBaseURI() )
            prologue = new Prologue(request.getPrefixMapping()) ;

        SerializationContext sCxt = new SerializationContext(prologue, new NodeToLabelMapBNode()) ;
        output(request, out, sCxt);
    }

    public static void output(UpdateRequest request, IndentedWriter out, SerializationContext sCxt)
    {
        UpdateWriter uw = new UpdateWriter(out, sCxt);
        uw.open();
        uw.update(request);
        uw.close();
    }

    public static void output(Update update, IndentedWriter out, SerializationContext sCxt)
    {
        UpdateWriter uw = new UpdateWriter(out, sCxt);
        uw.open();
        uw.update(update);
        uw.close();
    }
}
