/*
 * 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.rdf.model.impl;

import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.Locale ;

import org.apache.jena.rdf.model.* ;
import org.apache.jena.shared.UnknownPropertyException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** Writes out an XML serialization of a model.
 */
public class NTripleWriter extends Object implements RDFWriterI {

    RDFErrorHandler errorHandler = new RDFDefaultErrorHandler();

    protected static Logger logger = LoggerFactory.getLogger( NTripleWriter.class );

    public NTripleWriter() {
    }
    @Override
    public void write(Model model, OutputStream out, String base)
         {
        try {
            Writer w = new OutputStreamWriter(out, StandardCharsets.US_ASCII);
            write(model, w, base);
            w.flush();
        } catch (Exception ioe) {
            errorHandler.error(ioe);
        }
    }
    @Override
    public void write(Model model, Writer writer, String base)
         {
        try {
            PrintWriter pw;
            if (writer instanceof PrintWriter) {
                pw = (PrintWriter) writer;
            } else {
                pw = new PrintWriter(writer);
            }

            StmtIterator iter = model.listStatements();
            Statement stmt = null;

            while (iter.hasNext()) {
                stmt = iter.nextStatement();
                writeResource(stmt.getSubject(), pw);
                pw.print(" ");
                writeResource(stmt.getPredicate(), pw);
                pw.print(" ");
                writeNode(stmt.getObject(), pw);
                pw.println(" .");
            }
            pw.flush();
        } catch (Exception e) {
            errorHandler.error(e);
        }
    }

    /** Set a property to control the writer's behaviour.
     *
     * <p>This writer currently recognises no properties.  Invoking this
     * method always causes an <CODE>UnknownPropertyException</CODE>
     * to be raised.</p>?
     * @param propName The name of the property to be set
     * @param propValue The new value of the property
     * @return the previous value of the property
     */
    @Override
    public Object setProperty(String propName, Object propValue) {
        throw new UnknownPropertyException( propName );
    }

    public void setNsPrefix(String prefix, String ns) {
    }

    public String getPrefixFor( String uri )
        { return null; }

    @Override
    public RDFErrorHandler setErrorHandler(RDFErrorHandler errHandler) {
        RDFErrorHandler old = this.errorHandler;
        this.errorHandler = errHandler;
        return old;
    }

    public static void write(Model model, PrintWriter writer) {
        StmtIterator iter = model.listStatements();
        Statement stmt = null;

        while (iter.hasNext()) {
            stmt = iter.nextStatement();
            writeResource(stmt.getSubject(), writer);
            writer.print(" ");
            writeResource(stmt.getPredicate(), writer);
            writer.print(" ");
            writeNode(stmt.getObject(), writer);
            writer.println(" .");
        }
    }

    protected static void writeResource(Resource r, PrintWriter writer)
         {
        if (r.isAnon()) {
            writer.print(anonName(r.getId()));
        } else {
            writer.print("<");
            writeURIString(r.getURI(), writer);
            writer.print(">");
        }
    }
    static private boolean okURIChars[] = new boolean[128];
    static {
        for (int i = 32; i < 127; i++)
            okURIChars[i] = true;
        okURIChars['<'] = false;
        okURIChars['>'] = false;
        okURIChars['\\'] = false;

    }
    private static void writeURIString(String s, PrintWriter writer) {

        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c < okURIChars.length && okURIChars[c]) {
                writer.print(c);
            } else {
                String hexstr = Integer.toHexString(c).toUpperCase(Locale.ENGLISH);
                int pad = 4 - hexstr.length();
                writer.print("\\u");
                for (; pad > 0; pad--)
                    writer.print("0");
                writer.print(hexstr);
            }
        }
    }
    private static void writeString(String s, PrintWriter writer) {

        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c == '\\' || c == '"') {
                writer.print('\\');
                writer.print(c);
            } else if (c == '\n') {
                writer.print("\\n");
            } else if (c == '\r') {
                writer.print("\\r");
            } else if (c == '\t') {
                writer.print("\\t");
            } else if (c >= 32 && c < 127) {
                writer.print(c);
            } else {
                String hexstr = Integer.toHexString(c).toUpperCase();
                int pad = 4 - hexstr.length();
                writer.print("\\u");
                for (; pad > 0; pad--)
                    writer.print("0");
                writer.print(hexstr);
            }
        }
    }
    protected static void writeLiteral(Literal l, PrintWriter writer) {
        String s = l.getString();
        /*
        if (l.getWellFormed())
        	writer.print("xml");
        */
        writer.print('"');
        writeString(s, writer);
        writer.print('"');
        String lang = l.getLanguage();
        if (lang != null && !lang.equals(""))
            writer.print("@" + lang);
        String dt = l.getDatatypeURI();
        if (dt != null && !dt.equals(""))
            writer.print("^^<" + dt + ">");
    }

    protected static void writeNode(RDFNode n, PrintWriter writer)
         {
        if (n instanceof Literal) {
            writeLiteral((Literal) n, writer);
        } else {
            writeResource((Resource) n, writer);
        }
    }

    protected static String anonName(AnonId id) {
        String name = "_:A";
        String sid = id.toString();
        for (int i = 0; i < sid.length(); i++) {
            char c = sid.charAt(i);
            if (c == 'X') {
                name = name + "XX";
            } else if (Character.isLetterOrDigit(c)) {
                name = name + c;
            } else {
                name = name + "X" + Integer.toHexString(c) + "X";
            }
        }
        return name;
    }
}
