/*******************************************************************************
 * Copyright (c) 2011, 2014 Ericsson, Ecole Polytechnique de Montreal and others
 *
 * All rights reserved. This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0 which
 * accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors: Matthew Khouzam - Initial API and implementation
 * Contributors: Simon Marchi - Initial API and implementation
 *******************************************************************************/

package org.eclipse.tracecompass.ctf.core.event.types;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.ctf.core.CTFException;
import org.eclipse.tracecompass.ctf.core.event.io.BitBuffer;
import org.eclipse.tracecompass.ctf.core.event.scope.IDefinitionScope;

/**
 * A CTF string declaration.
 *
 * Strings are an array of bytes of variable size and are terminated by a '\0'
 * "NULL" character. Their encoding is described in the TSDL meta-data. In
 * absence of encoding attribute information, the default encoding is UTF-8.
 *
 * @version 1.0
 * @author Matthew Khouzam
 * @author Simon Marchi
 */
@NonNullByDefault
public class StringDeclaration extends Declaration {

    private static final StringDeclaration STRING_DEC_UTF8 = new StringDeclaration(Encoding.UTF8);
    private static final StringDeclaration STRING_DEC_ASCII = new StringDeclaration(Encoding.ASCII);
    private static final StringDeclaration STRING_DEC_NO_ENC = new StringDeclaration(Encoding.NONE);

    // ------------------------------------------------------------------------
    // Attributes
    // ------------------------------------------------------------------------

    private static final int BITS_PER_BYTE = Byte.SIZE;
    private final Encoding fEncoding;

    // ------------------------------------------------------------------------
    // Constructors
    // ------------------------------------------------------------------------

    /**
     * Generate an encoded string declaration
     *
     * @param encoding
     *            the encoding, utf8 or ascii
     */
    private StringDeclaration(Encoding encoding) {
        fEncoding = encoding;
    }

    /**
     * Create a StringDeclaration with the default UTF-8 encoding
     *
     * @return a {@link StringDeclaration} with UTF-8 encoding
     */
    public static StringDeclaration getStringDeclaration() {
        return STRING_DEC_UTF8;
    }

    /**
     * Create a StringDeclaration
     *
     * @param encoding
     *            the {@link Encoding} can be Encoding.UTF8, Encoding.ASCII or
     *            other
     * @return a {@link StringDeclaration}
     * @throws IllegalArgumentException
     *             if the encoding is not recognized.
     */
    public static StringDeclaration getStringDeclaration(Encoding encoding) {
        switch (encoding) {
        case ASCII:
            return STRING_DEC_ASCII;
        case NONE:
            return STRING_DEC_NO_ENC;
        case UTF8:
            return STRING_DEC_UTF8;
        default:
            throw new IllegalArgumentException("Unrecognized encoding: " + encoding); //$NON-NLS-1$
        }
    }

    // ------------------------------------------------------------------------
    // Getters/Setters/Predicates
    // ------------------------------------------------------------------------

    /**
     *
     * @return the character encoding.
     */
    public Encoding getEncoding() {
        return fEncoding;
    }

    @Override
    public long getAlignment() {
        // See ctf 4.2.5: Strings are always aligned on byte size.
        return BITS_PER_BYTE;
    }

    @Override
    public int getMaximumSize() {
        /*
         * Every definition can have a different size, so we do not scope this.
         * Minimum size is one byte (8 bits) though.
         */
        return 8;
    }

    // ------------------------------------------------------------------------
    // Operations
    // ------------------------------------------------------------------------

    @Override
    public StringDefinition createDefinition(@Nullable IDefinitionScope definitionScope,
            String fieldName, BitBuffer input) throws CTFException {
        String value = read(input);
        return new StringDefinition(this, definitionScope, fieldName, value);
    }

    private String read(BitBuffer input) throws CTFException {
        /* Offset the buffer position wrt the current alignment */
        alignRead(input);

        StringBuilder sb = new StringBuilder();
        char c = (char) input.get(BITS_PER_BYTE, false);
        while (c != 0) {
            sb.append(c);
            c = (char) input.get(BITS_PER_BYTE, false);
        }
        return sb.toString();
    }

    @Override
    public String toString() {
        /* Only used for debugging */
        return "[declaration] string[" + Integer.toHexString(hashCode()) + ']'; //$NON-NLS-1$
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = prime;
        switch (fEncoding) {
        case ASCII:
            result += 1;
            break;
        case NONE:
            result += 2;
            break;
        case UTF8:
            result += 3;
            break;
        default:
            break;
        }
        return result;
    }

    @Override
    public boolean equals(@Nullable Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        StringDeclaration other = (StringDeclaration) obj;
        return (fEncoding == other.fEncoding);
    }

    @Override
    public boolean isBinaryEquivalent(@Nullable IDeclaration other) {
        return equals(other);
    }

}
