/* 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.pig.backend.hadoop.accumulo;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.Map;

import org.apache.pig.LoadStoreCaster;
import org.apache.pig.ResourceSchema.ResourceFieldSchema;
import org.apache.pig.backend.executionengine.ExecException;
import org.apache.pig.data.DataBag;
import org.apache.pig.data.DataByteArray;
import org.apache.pig.data.Tuple;
import org.joda.time.DateTime;

import com.google.common.base.Preconditions;

/**
 * A LoadStoreCaster implementation which stores most type implementations as
 * bytes generated from the toString representation with a UTF8 Charset. Pulled
 * some implementations from the Accumulo Lexicoder implementations in 1.6.0.
 */
public class AccumuloBinaryConverter implements LoadStoreCaster {
    private static final int SIZE_OF_INT = Integer.SIZE / Byte.SIZE;
    private static final int SIZE_OF_LONG = Long.SIZE / Byte.SIZE;

    /**
     * NOT IMPLEMENTED
     */
    @Override
    public DataBag bytesToBag(byte[] b, ResourceFieldSchema fieldSchema)
            throws IOException {
        throw new ExecException("Can't generate DataBags from byte[]");
    }

    @Override
    public BigDecimal bytesToBigDecimal(byte[] b) throws IOException {
        throw new ExecException("Can't generate a BigInteger from byte[]");
    }

    @Override
    public BigInteger bytesToBigInteger(byte[] b) throws IOException {
        // Taken from Accumulo's BigIntegerLexicoder in 1.6.0
        DataInputStream dis = new DataInputStream(new ByteArrayInputStream(b));
        int len = dis.readInt();
        len = len ^ 0x80000000;
        len = Math.abs(len);

        byte[] bytes = new byte[len];
        dis.readFully(bytes);

        bytes[0] = (byte) (0x80 ^ bytes[0]);

        return new BigInteger(bytes);
    }

    @Override
    public Boolean bytesToBoolean(byte[] b) throws IOException {
        Preconditions.checkArgument(1 == b.length);
        return b[0] == (byte) 1;
    }

    @Override
    public String bytesToCharArray(byte[] b) throws IOException {
        return new String(b, StandardCharsets.UTF_8);
    }

    /**
     * NOT IMPLEMENTED
     */
    @Override
    public DateTime bytesToDateTime(byte[] b) throws IOException {
        String s = new String(b, StandardCharsets.UTF_8);
        return DateTime.parse(s);
    }

    @Override
    public Double bytesToDouble(byte[] b) throws IOException {
        return Double.longBitsToDouble(bytesToLong(b));
    }

    @Override
    public Float bytesToFloat(byte[] b) throws IOException {
        return Float.intBitsToFloat(bytesToInteger(b));
    }

    @Override
    public Integer bytesToInteger(byte[] b) throws IOException {
        Preconditions.checkArgument(b.length == SIZE_OF_INT);
        int n = 0;
        for (int i = 0; i < b.length; i++) {
            n <<= 8;
            n ^= b[i] & 0xFF;
        }
        return n;
    }

    @Override
    public Long bytesToLong(byte[] b) throws IOException {
        Preconditions.checkArgument(b.length == SIZE_OF_LONG);
        long l = 0;
        for (int i = 0; i < b.length; i++) {
            l <<= 8;
            l ^= b[i] & 0xFF;
        }
        return l;
    }

    /**
     * NOT IMPLEMENTED
     */
    @Override
    public Map<String, Object> bytesToMap(byte[] b,
            ResourceFieldSchema fieldSchema) throws IOException {
        throw new ExecException("Can't generate Map from byte[]");
    }

    /**
     * NOT IMPLEMENTED
     */
    @Override
    public Tuple bytesToTuple(byte[] b, ResourceFieldSchema fieldSchema)
            throws IOException {
        throw new ExecException("Can't generate a Tuple from byte[]");
    }

    /**
     * Not implemented!
     */
    @Override
    public byte[] toBytes(BigDecimal bd) throws IOException {
        throw new IOException("Can't generate bytes from BigDecimal");
    }

    @Override
    public byte[] toBytes(BigInteger bi) throws IOException {
        // Taken from Accumulo's BigIntegerLexicoder in 1.6.0
        byte[] bytes = bi.toByteArray();

        byte[] ret = new byte[4 + bytes.length];

        DataOutputStream dos = new DataOutputStream(
                new FixedByteArrayOutputStream(ret));

        // flip the sign bit
        bytes[0] = (byte) (0x80 ^ bytes[0]);

        int len = bytes.length;
        if (bi.signum() < 0)
            len = -len;

        len = len ^ 0x80000000;

        try {
            dos.writeInt(len);
            dos.write(bytes);
        } finally {
            dos.close();
        }

        return ret;
    }

    @Override
    public byte[] toBytes(Boolean b) throws IOException {
        return new byte[] { b ? (byte) 1 : (byte) 0 };
    }

    /**
     * NOT IMPLEMENTED
     */
    @Override
    public byte[] toBytes(DataBag bag) throws IOException {
        throw new ExecException("Cant' generate bytes from DataBag");
    }

    @Override
    public byte[] toBytes(DataByteArray a) throws IOException {
        return a.get();
    }

    @Override
    public byte[] toBytes(DateTime dt) throws IOException {
        return dt.toString().getBytes(StandardCharsets.UTF_8);
    }

    @Override
    public byte[] toBytes(Double d) throws IOException {
        return toBytes(Double.doubleToRawLongBits(d));
    }

    @Override
    public byte[] toBytes(Float f) throws IOException {
        return toBytes(Float.floatToRawIntBits(f));
    }

    @Override
    public byte[] toBytes(Integer val) throws IOException {
        int intVal = val.intValue();
        byte[] b = new byte[4];
        for (int i = 3; i > 0; i--) {
            b[i] = (byte) intVal;
            intVal >>>= 8;
        }
        b[0] = (byte) intVal;
        return b;
    }

    @Override
    public byte[] toBytes(Long val) throws IOException {
        long longVal = val.longValue();
        byte[] b = new byte[8];
        for (int i = 7; i > 0; i--) {
            b[i] = (byte) longVal;
            longVal >>>= 8;
        }
        b[0] = (byte) longVal;
        return b;
    }

    /**
     * NOT IMPLEMENTED
     */
    @Override
    public byte[] toBytes(Map<String, Object> m) throws IOException {
        throw new IOException("Can't generate bytes from Map");
    }

    @Override
    public byte[] toBytes(String s) throws IOException {
        return s.getBytes(StandardCharsets.UTF_8);
    }

    /**
     * NOT IMPLEMENTED
     */
    @Override
    public byte[] toBytes(Tuple t) throws IOException {
        throw new IOException("Can't generate bytes from Tuple");
    }
}
