package com.fasterxml.jackson.core.io;

import java.io.*;
import java.nio.ByteBuffer;

import com.fasterxml.jackson.core.SerializableString;
import com.fasterxml.jackson.core.util.BufferRecyclers;

/**
 * String token that can lazily serialize String contained and then reuse that
 * serialization later on. This is similar to JDBC prepared statements, for example,
 * in that instances should only be created when they are used more than use;
 * prime candidates are various serializers.
 *<p>
 * Class is final for performance reasons and since this is not designed to
 * be extensible or customizable (customizations would occur in calling code)
 */
public class SerializedString
    implements SerializableString, java.io.Serializable
{
    private static final long serialVersionUID = 1L;

    protected final String _value;

    /* 13-Dec-2010, tatu: Whether use volatile or not is actually an important
     *   decision for multi-core use cases. Cost of volatility can be non-trivial
     *   for heavy use cases, and serialized-string instances are accessed often.
     *   Given that all code paths with common Jackson usage patterns go through
     *   a few memory barriers (mostly with cache/reuse pool access) it seems safe
     *   enough to omit volatiles here, given how simple lazy initialization is.
     *   This can be compared to how {@link String#hashCode} works; lazily and
     *   without synchronization or use of volatile keyword.
     *
     *   Change to remove volatile was a request by implementors of a high-throughput
     *   search framework; and they believed this is an important optimization for
     *   heaviest, multi-core deployed use cases.
     */
    /*
     * 22-Sep-2013, tatu: FWIW, there have been no reports of problems in this
     *   area, or anything pointing to it. So I think we are safe up to JDK7
     *   and hopefully beyond.
     */
    
    protected /*volatile*/ byte[] _quotedUTF8Ref;

    protected /*volatile*/ byte[] _unquotedUTF8Ref;

    protected /*volatile*/ char[] _quotedChars;

    public SerializedString(String v) {
        if (v == null) {
            throw new IllegalStateException("Null String illegal for SerializedString");
        }
        _value = v;
    }
    
    /*
    /**********************************************************
    /* Serializable overrides
    /**********************************************************
     */

    /**
     * Ugly hack, to work through the requirement that _value is indeed final,
     * and that JDK serialization won't call ctor(s).
     * 
     * @since 2.1
     */
    protected transient String _jdkSerializeValue;

    private void readObject(ObjectInputStream in) throws IOException {
        _jdkSerializeValue = in.readUTF();
    }

    private void writeObject(ObjectOutputStream out) throws IOException {
        out.writeUTF(_value);
    }

    protected Object readResolve() {
        return new SerializedString(_jdkSerializeValue);
    }

    /*
    /**********************************************************
    /* API
    /**********************************************************
     */

    @Override
    public final String getValue() { return _value; }
    
    /**
     * Returns length of the String as characters
     */
    @Override
    public final int charLength() { return _value.length(); }
    
    @Override
    public final char[] asQuotedChars() {
        char[] result = _quotedChars;
        if (result == null) {
            result = BufferRecyclers.quoteAsJsonText(_value);
            _quotedChars = result;
        }
        return result;
    }

    /**
     * Accessor for accessing value that has been quoted using JSON
     * quoting rules, and encoded using UTF-8 encoding.
     */
    @Override
    public final byte[] asUnquotedUTF8() {
        byte[] result = _unquotedUTF8Ref;
        if (result == null) {
            result = BufferRecyclers.encodeAsUTF8(_value);
            _unquotedUTF8Ref  = result;
        }
        return result;
    }

    /**
     * Accessor for accessing value as is (without JSON quoting)
     * encoded using UTF-8 encoding.
     */
    @Override
    public final byte[] asQuotedUTF8() {
        byte[] result = _quotedUTF8Ref;
        if (result == null) {
            result = BufferRecyclers.quoteAsJsonUTF8(_value);
            _quotedUTF8Ref = result;
        }
        return result;
    }

    /*
    /**********************************************************
    /* Additional 2.0 methods for appending/writing contents
    /**********************************************************
     */

    @Override
    public int appendQuotedUTF8(byte[] buffer, int offset) {
        byte[] result = _quotedUTF8Ref;
        if (result == null) {
            result = BufferRecyclers.quoteAsJsonUTF8(_value);
            _quotedUTF8Ref = result;
        }
        final int length = result.length;
        if ((offset + length) > buffer.length) {
            return -1;
        }
        System.arraycopy(result, 0, buffer, offset, length);
        return length;
    }

    @Override
    public int appendQuoted(char[] buffer, int offset) {
        char[] result = _quotedChars;
        if (result == null) {
            result = BufferRecyclers.quoteAsJsonText(_value);
            _quotedChars = result;
        }
        final int length = result.length;
        if ((offset + length) > buffer.length) {
            return -1;
        }
        System.arraycopy(result, 0, buffer, offset, length);
        return length;
    }

    @Override
    public int appendUnquotedUTF8(byte[] buffer, int offset) {
        byte[] result = _unquotedUTF8Ref;
        if (result == null) {
            result = BufferRecyclers.encodeAsUTF8(_value);
            _unquotedUTF8Ref  = result;
        }
        final int length = result.length;
        if ((offset + length) > buffer.length) {
            return -1;
        }
        System.arraycopy(result, 0, buffer, offset, length);
        return length;
    }

    @Override
    public int appendUnquoted(char[] buffer, int offset) {
        String str = _value;
        final int length = str.length();
        if ((offset + length) > buffer.length) {
            return -1;
        }
        str.getChars(0,  length, buffer, offset);
        return length;
    }

    @Override
    public int writeQuotedUTF8(OutputStream out) throws IOException {
        byte[] result = _quotedUTF8Ref;
        if (result == null) {
            result = BufferRecyclers.quoteAsJsonUTF8(_value);
            _quotedUTF8Ref = result;
        }
        final int length = result.length;
        out.write(result, 0, length);
        return length;
    }

    @Override
    public int writeUnquotedUTF8(OutputStream out) throws IOException {
        byte[] result = _unquotedUTF8Ref;
        if (result == null) {
            result = BufferRecyclers.encodeAsUTF8(_value);
            _unquotedUTF8Ref  = result;
        }
        final int length = result.length;
        out.write(result, 0, length);
        return length;
    }

    @Override
    public int putQuotedUTF8(ByteBuffer buffer) {
        byte[] result = _quotedUTF8Ref;
        if (result == null) {
            result = BufferRecyclers.quoteAsJsonUTF8(_value);
            _quotedUTF8Ref = result;
        }
        final int length = result.length;
        if (length > buffer.remaining()) {
            return -1;
        }
        buffer.put(result, 0, length);
        return length;
    }

    @Override
    public int putUnquotedUTF8(ByteBuffer buffer) {
        byte[] result = _unquotedUTF8Ref;
        if (result == null) {
            result = BufferRecyclers.encodeAsUTF8(_value);
            _unquotedUTF8Ref  = result;
        }
        final int length = result.length;
        if (length > buffer.remaining()) {
            return -1;
        }
        buffer.put(result, 0, length);
        return length;
    }

    
    /*
    /**********************************************************
    /* Standard method overrides
    /**********************************************************
     */

    @Override
    public final String toString() { return _value; }
    
    @Override
    public final int hashCode() { return _value.hashCode(); }

    @Override
    public final boolean equals(Object o) {
        if (o == this) return true;
        if (o == null || o.getClass() != getClass()) return false;
        SerializedString other = (SerializedString) o;
        return _value.equals(other._value);
    }
}
