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

import org.apache.jena.datatypes.DatatypeFormatException ;
import org.apache.jena.datatypes.RDFDatatype ;
import org.apache.jena.datatypes.xsd.XSDDatatype ;
import org.apache.jena.enhanced.EnhGraph;
import org.apache.jena.enhanced.EnhNode;
import org.apache.jena.enhanced.Implementation;
import org.apache.jena.graph.Node;
import org.apache.jena.graph.TextDirection;
import org.apache.jena.rdf.model.* ;
import org.apache.jena.shared.BadBooleanException;
import org.apache.jena.shared.BadCharLiteralException;
import org.apache.jena.shared.PrefixMapping;

/** An implementation of Literal.
 */
public class LiteralImpl extends EnhNode implements Literal {

    final static public Implementation factory = new Implementation() {
        @Override public boolean canWrap( Node n, EnhGraph eg )
            { return n.isLiteral(); }

        @Override public EnhNode wrap(Node n, EnhGraph eg) {
            if (!n.isLiteral()) throw new LiteralRequiredException( n );
            return new LiteralImpl(n,eg);
        }
    };

    public LiteralImpl( Node n, ModelCom m) {
        super( n, m );
    }

    public LiteralImpl( Node n, EnhGraph m ) {
        super( n, m );
    }

    @Override
    public Object visitWith( RDFVisitor rv )
        { return rv.visitLiteral( this ); }

    /**
        Literals are not in any particular model, and so inModel can return this.
        @param m a model to move the literal into
        @return this
    */
    @Override
    public Literal inModel( Model m )
        {
        return getModel() == m
            ? this
            : (Literal) m.getRDFNode( asNode() )
            ;
         }

    @Override
    public Literal asLiteral()
        { return this; }

    @Override
    public Resource asResource()
        { throw new ResourceRequiredException( asNode() ); }

    @Override
    public StatementTerm asStatementTerm()
        { throw new StmtTermRequiredException( asNode() ); }

    /**
        Answer the model this literal was created in, if any, otherwise null.
    */
    @Override
    public Model getModel()
        { return (ModelCom) getGraph(); }

    @Override public String toString() {
        // Code has depended on toString() on a string literal being the
        // string itself, not quoted or escaped.
        // Jena5 is now clean but applications may also have the same assumption.
        // Jena 2,3,4 --
        // return asNode().getLiteral().toString( PrefixMapping.Standard, false );
        if ( Util.isSimpleString(this) )
            return getLexicalForm();
        return asNode().toString(PrefixMapping.Standard);
    }

    /**
     * Return the value of the literal. In the case of plain literals
     * this will return the literal string. In the case of typed literals
     * it will return a java object representing the value. In the case
     * of typed literals representing a java primitive then the appropriate
     * java wrapper class (Integer etc) will be returned.
     */
    @Override
    public Object getValue() {
        return asNode().getLiteralValue();
    }

    /**
     * Return the datatype of the literal. This will be null in the
     * case of plain literals.
     */
    @Override
    public RDFDatatype getDatatype() {
        return asNode().getLiteralDatatype();
    }

    /**
     * Return the uri of the datatype of the literal. This will be null in the
     * case of plain literals.
     */
    @Override
    public String getDatatypeURI() {
        return asNode().getLiteralDatatypeURI();
    }

    /**
     * Return true if this is a "plain" (i.e. old style, not typed) literal.
     * For RDF 1.1, the most compatible choice is "xsd:string" or "rdf:langString".
     */
    private boolean isPlainLiteral() {
        return Util.isLangString(this) || Util.isSimpleString(this) ;
    }

    /**
     * Return the lexical form of the literal.
     */
    @Override
    public String getLexicalForm() {
        return asNode().getLiteralLexicalForm();
    }

    @Override
    public boolean getBoolean()  {
        Object value = asNode().getLiteralValue();
        if (isPlainLiteral()) {
            // old style plain literal - try parsing the string
            if (value.equals("true")) {
                return true;
            } else if (value.equals("false")) {
                return false;
            } else {
                throw new BadBooleanException( value.toString() );
            }
        } else {
            // typed literal
            if (value instanceof Boolean) {
                return (Boolean) value;
            } else {
                throw new DatatypeFormatException(this.toString() + " is not a Boolean");
            }
        }
    }

    @Override
    public byte getByte()  {
        if (isPlainLiteral()) {
            return Byte.parseByte(getLexicalForm());
        } else {
            return byteValue( asNumber( getValue() ) );
        }
    }


    @Override
    public short getShort()  {
        if (isPlainLiteral()) {
            return Short.parseShort(getLexicalForm());
        } else {
            return shortValue( asNumber( getValue() ) );
        }
    }

    @Override
    public int getInt()  {
        if (isPlainLiteral()) {
            return Integer.parseInt(getLexicalForm());
        } else {
            return intValue( asNumber( getValue() ) );
        }
    }

    @Override
    public long getLong()  {
        if (isPlainLiteral()) {
            return Long.parseLong(getLexicalForm());
        } else {
            return asNumber(getValue()).longValue();
        }
    }

    @Override
    public char getChar()  {
        if (isPlainLiteral()) {
            if (getString().length()==1) {
                return (getString().charAt(0));
            } else {
                throw new BadCharLiteralException( getString() );
            }
        } else {
            Object value = getValue();
            if (value instanceof Character) {
                return (Character) value;
            } else {
                throw new DatatypeFormatException(value.toString() + " is not a Character");
            }
        }
    }

    @Override
    public float getFloat()  {
        if (isPlainLiteral()) {
            return Float.parseFloat(getLexicalForm());
        } else {
            return asNumber(getValue()).floatValue();
        }
    }

    @Override
    public double getDouble()  {
        if (isPlainLiteral()) {
            return Double.parseDouble(getLexicalForm());
        } else {
            return asNumber(getValue()).doubleValue();
        }
    }

    @Override
    public String getString()  {
        return asNode().getLiteralLexicalForm();
    }

    @Override
    public String getLanguage() {
        return asNode().getLiteralLanguage();
    }

    @Override
    public String getBaseDirection() {
        TextDirection textDir = asNode().getLiteralBaseDirection();
        if ( textDir == null )
            return null;
        return textDir.direction();
    }


    /**
     * Test that two literals are semantically equivalent.
     * In some cases this may be the same as equals, in others
     * equals is stricter. For example, two xsd:int literals with
     * the same value but different language tag are semantically
     * equivalent but distinguished by the java equality function
     * in order to support round tripping.
     */
    @Override
    public boolean sameValueAs(Literal other) {
        return asNode().sameValueAs(other.asNode());
    }

     // Internal helper method to convert a value to number
    private Number asNumber(Object value) {
        if (value instanceof Number) {
            return ((Number)value);
        } else {
            StringBuilder message = new StringBuilder("Error converting typed value to a number. \n");
            message.append("Datatype is: " + getDatatypeURI());
            if ( getDatatypeURI() == null || ! getDatatypeURI().startsWith(XSDDatatype.XSD)) {
                message.append(" which is not an xsd type.");
            }
            message.append(" \n");
            message.append("Java representation type is " + (value == null ? "null" : value.getClass().toString()));
            throw new DatatypeFormatException(message.toString());
        }
    }
    private byte byteValue( Number n )
        {
        return (byte) getIntegralValueInRange( Byte.MIN_VALUE, n, Byte.MAX_VALUE );
        }

    private short shortValue( Number n )
        {
        return (short) getIntegralValueInRange( Short.MIN_VALUE, n, Short.MAX_VALUE );
        }

    private int intValue( Number n )
        {
        return (int) getIntegralValueInRange( Integer.MIN_VALUE, n, Integer.MAX_VALUE );
        }

    private long getIntegralValueInRange( long min, Number n, long max )
        {
        long result = n.longValue();
        if (min <= result && result <= max) return result;
        throw new IllegalArgumentException( "byte value required: " + result );
        }

}
