package com.legstar.base.type.primitive;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;

import com.legstar.base.context.CobolContext;

/**
 * COBOL Double type.
 * <p/>
 * This is the bit layout for this type:
 * 
 * <pre>
6666555555555544444444443333333333222222222211111111110000000000
3210987654321098765432109876543210987654321098765432109876543210
----------------------------------------------------------------
0100010101010100001110101110100100010101101101010111001111101000
-                                                                sign
 -------                                                         excess
        -------------------------------------------------------  mantissa
 * </pre>
 *
 */
public class CobolDoubleType<T extends Number> extends CobolPrimitiveType < T > {

    public FromHostPrimitiveResult < T > fromHost(Class < T > javaClass,
            CobolContext cobolContext, byte[] hostData, int start) {

        int hostBytesLen = getBytesLen();
        if (hostData.length < start + hostBytesLen) {
            return new FromHostPrimitiveResult < T >("Length provided "
                    + (hostData.length - start)
                    + " is smaller than the required " + hostBytesLen, hostData,
                    start, hostBytesLen);
        }

        ByteBuffer bb = ByteBuffer.wrap(hostData, start, hostBytesLen);
        long hostLongBits = bb.getLong();

        /* First bit left (bit 63) is the sign: 0 = positive, 1 = negative */
        int sign = (int) ((hostLongBits & 0x8000000000000000L) >>> 63);

        /*
         * Bits 62-56 (7 bits) represents the exponent offset by 64, this
         * number is called excess so you get the exponent as
         * E= excess - 64
         */
        int excess = (int) ((hostLongBits & 0x7f00000000000000L) >>> 56);
        int exponent = excess == 0 ? 0 : (excess - 64);

        /* Bits 55-0 (56 bits) represents the mantissa. */
        long mantissa = hostLongBits & 0x00ffffffffffffffL;

        /* Java Doubles are in IEEE 754 floating-point bit layout. */
        /*
         * Host exponent is hexadecimal based while java is binary based.
         * There is also an additional shift for non-zero values due to
         * the 1-plus" normalized java specs.
         */
        if (mantissa != 0) {
            exponent = ((4 * exponent) - 1);
        }

        /*
         * The java mantissa is 53 bits while the host is 56. This
         * means there is a systematic loss of precision.
         */
        mantissa = mantissa >>> 3;
        
        /* In java the 53th bit needs to be one */
        while (mantissa > 0L && (mantissa & 0x0010000000000000L) == 0) {
            mantissa = mantissa << 1;
            exponent = exponent - 1;
        }

        /* First check if this is a zero value */
        double result = 0d;
        if (exponent != 0 || mantissa != 0) {
            /* Get rid of the leading 1 which needs to be implicit */
            long javaLongBits = mantissa & 0x000fffffffffffffL;
            javaLongBits = javaLongBits | ((long) (exponent + 1023) << 52);
            javaLongBits = javaLongBits | ((long) sign << 63);
            result = Double.longBitsToDouble(javaLongBits);
        }

        return new FromHostPrimitiveResult < T >(valueOf(javaClass, result));
    }

    @SuppressWarnings("unchecked")
    public static <D extends Number> D valueOf(Class < D > clazz, Double value) {
        if (clazz.equals(Double.class)) {
            return (D) value;
        } else if (clazz.equals(Float.class)) {
            return (D) Float.valueOf(value.floatValue());
        } else if (clazz.equals(Short.class)) {
            return (D) Short.valueOf(value.shortValue());
        } else if (clazz.equals(Integer.class)) {
            return (D) Integer.valueOf(value.intValue());
        } else if (clazz.equals(Long.class)) {
            return (D) Long.valueOf(value.longValue());
        } else if (clazz.equals(BigDecimal.class)) {
            return (D) new BigDecimal(value);
        } else if (clazz.equals(BigInteger.class)) {
            return (D) BigInteger.valueOf(value.longValue());
        }
        throw new IllegalArgumentException("Unsupported java type " + clazz);
    }

    public boolean isValid(Class < T > javaClass, CobolContext cobolContext,
            byte[] hostData, int start) {
        int bytesLen = getBytesLen();

        // Is buffer large enough to contain this type?
        // TODO last field in a record might be truncated if all low-values or
        // spaces
        if (hostData.length < start + bytesLen) {
            return false;
        }
        // FIXME requires specific validation
        return true;
    }

    public int getBytesLen() {
        return 8;
    }

    // -----------------------------------------------------------------------------
    // Builder section
    // -----------------------------------------------------------------------------
    public static class Builder<T extends Number> extends
            CobolPrimitiveType.Builder < T, Builder < T > > {

        public Builder(Class < T > clazz) {
            super(clazz);
        }

        public CobolDoubleType < T > build() {
            return new CobolDoubleType < T >(this);
        }

        protected Builder < T > self() {
            return this;
        }

    }

    // -----------------------------------------------------------------------------
    // Constructor
    // -----------------------------------------------------------------------------
    private CobolDoubleType(Builder < T > builder) {

        super(builder);

    }

}