/*
 * 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.riot.out;

import java.net.MalformedURLException ;
import java.util.Map ;

import org.apache.jena.atlas.io.IndentedLineBuffer ;
import org.apache.jena.atlas.io.IndentedWriter ;
import org.apache.jena.atlas.lib.Bytes ;
import org.apache.jena.atlas.lib.Chars ;
import org.apache.jena.graph.Node ;
import org.apache.jena.graph.Triple ;
import org.apache.jena.iri.IRI ;
import org.apache.jena.iri.IRIRelativize ;
import org.apache.jena.rdf.model.RDFNode ;
import org.apache.jena.riot.system.* ;
import org.apache.jena.shared.PrefixMapping ;
import org.apache.jena.sparql.ARQConstants ;
import org.apache.jena.sparql.core.Quad ;

/** Presentation utilities for Nodes, Triples, Quads and more.
 * <p>
 * Methods <tt>str</tt> generate a reparsable string.
 * <p>
 * Methods <tt>displayStr</tt> do not guarantee a reparsable string 
 * e.g. may use abbreviations or common prefixes.   
 */
public class NodeFmtLib
{
    // Replaces FmtUtils
    // See OutputLangUtils.
    // See and use EscapeStr
    
    private static final NodeFormatter plainFormatter = new NodeFormatterNT() ;
    
    private static PrefixMap dftPrefixMap = PrefixMapFactory.create() ;
    static {
        PrefixMapping pm = ARQConstants.getGlobalPrefixMap() ;
        Map<String, String> map = pm.getNsPrefixMap() ;
        for ( Map.Entry<String, String> e : map.entrySet() )
            dftPrefixMap.add(e.getKey(), e.getValue() ) ;
    }

    public static String str(Triple t)
    {
        return strNodes(t.getSubject(), t.getPredicate(),t.getObject()) ;
    }

    public static String str(Quad q)
    {
        return strNodes(q.getGraph(), q.getSubject(), q.getPredicate(), q.getObject()) ;
    }

    public static String str(Node n)
    {
        IndentedLineBuffer sw = new IndentedLineBuffer() ;
        str(sw, n) ;
        return sw.toString() ; 
    }

    /** A displayable string for an RDFNode. Includes common abbreviations */
    public static String displayStr(RDFNode obj)
    {
        return displayStr(obj.asNode()) ;
    }
    
    public static String displayStr(Node n)
    { 
        return str(n, null, dftPrefixMap) ;
    }

    
    // Worker
    public static String strNodes(Node ... nodes)
    {
        IndentedLineBuffer sw = new IndentedLineBuffer() ;
        boolean first = true ;
        for ( Node n : nodes ) 
        {
            if ( ! first )
                sw.append(" ") ;
            first = false ;
            if ( n == null ) {
                sw.append("null") ;
                continue;
            }
            str(sw, n) ;
        }
        return sw.toString() ; 
    }

    //public static String displayStr(Node n) { return serialize(n) ; }

    public static void str(IndentedWriter w, Node n)
    { serialize(w, n, null, null) ; }

    public static String str(Node n, Prologue prologue)
    {
        return str(n, prologue.getBaseURI(), prologue.getPrefixMap()) ;
    }

    public static String str(Node n, String base, PrefixMap prefixMap)
    {
        IndentedLineBuffer sw = new IndentedLineBuffer() ;
        serialize(sw, n, base, prefixMap) ;
        return sw.toString() ;
    }

    public static void serialize(IndentedWriter w, Node n, Prologue prologue)
    { serialize(w, n, prologue.getBaseURI(), prologue.getPrefixMap()) ; }
    
    public static void serialize(IndentedWriter w, Node n, String base, PrefixMap prefixMap)
    {
        NodeFormatter formatter ;
        if ( base == null && prefixMap == null )
            formatter = plainFormatter ;
        else 
            formatter = new NodeFormatterTTL(base, prefixMap) ;
        formatter.format(w, n) ;
    }
    
    // ---- Blank node labels.
    
    // Strict N-triples only allows [A-Za-z][A-Za-z0-9]
    static char encodeMarkerChar = 'X' ;

    // These two form a pair to convert bNode labels to a safe (i.e. legal N-triples form) and back agains. 
    
    // Encoding is:
    // 1 - Add a Letter 
    // 2 - Hexify, as Xnn, anything outside ASCII A-Za-z0-9
    // 3 - X is encoded as XX
    
    private static char LabelLeadingLetter = 'B' ; 
    
    public static String encodeBNodeLabel(String label)
    {
        StringBuilder buff = new StringBuilder() ;
        // Must be at least one char and not a digit.
        buff.append(LabelLeadingLetter) ;
        
        for ( int i = 0 ; i < label.length() ; i++ )
        {
            char ch = label.charAt(i) ;
            if ( ch == encodeMarkerChar )
            {
                buff.append(ch) ;
                buff.append(ch) ;
            }
            else if ( RiotChars.isA2ZN(ch) )
                buff.append(ch) ;
            else
                Chars.encodeAsHex(buff, encodeMarkerChar, ch) ;
        }
        return buff.toString() ;
    }

    // Assumes that blank nodes only have characters in the range of 0-255
    public static String decodeBNodeLabel(String label)
    {
        StringBuilder buffer = new StringBuilder() ;

        if ( label.charAt(0) != LabelLeadingLetter )
            return label ;
        
        // Skip first.
        for ( int i = 1; i < label.length(); i++ )
        {
            char ch = label.charAt(i) ;
            
            if ( ch != encodeMarkerChar )
            {
                buffer.append(ch) ;
            }
            else
            {
                // Maybe XX or Xnn.
                char ch2 = label.charAt(i+1) ;
                if ( ch2 == encodeMarkerChar )
                {
                    i++ ;
                    buffer.append(ch) ;
                    continue ;
                }
                
                // Xnn
                i++ ;
                char hiC = label.charAt(i) ;
                int hi = Bytes.hexCharToInt(hiC) ;
                i++ ;
                char loC = label.charAt(i) ;
                int lo = Bytes.hexCharToInt(loC) ;

                int combined = ((hi << 4) | lo) ;
                buffer.append((char) combined) ;
            }
        }

        return buffer.toString() ;
    }
    
    // ---- Relative URIs.
    
    static private int relFlags = IRIRelativize.SAMEDOCUMENT | IRIRelativize.CHILD ;
    static public String abbrevByBase(String uri, String base)
    {
        if ( base == null )
            return null ;
        IRI baseIRI = IRIResolver.iriFactory().construct(base) ;
        IRI rel = baseIRI.relativize(uri, relFlags) ;
        String r = null ;
        try { r = rel.toASCIIString() ; }
        catch (MalformedURLException  ex) { r = rel.toString() ; }
        return r ;
    }
}
