/*
 * Copyright 2010, 2011, 2012, 2013 mapsforge.org
 *
 * This program is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.mapsforge.map.writer;

import java.security.InvalidParameterException;

/**
 * This class converts numbers to byte arrays.
 */
public final class Serializer {
    /**
     * Converts a signed int to a byte array.
     * <p/>
     * The byte order is big-endian.
     *
     * @param value the int value.
     * @return an array with four bytes.
     */
    public static byte[] getBytes(int value) {
        return new byte[]{(byte) (value >> 24), (byte) (value >> 16), (byte) (value >> 8), (byte) value};
    }

    /**
     * Converts a signed long to a byte array.
     * <p/>
     * The byte order is big-endian.
     *
     * @param value the long value.
     * @return an array with eight bytes.
     */
    public static byte[] getBytes(long value) {
        return new byte[]{(byte) (value >> 56), (byte) (value >> 48), (byte) (value >> 40), (byte) (value >> 32),
                (byte) (value >> 24), (byte) (value >> 16), (byte) (value >> 8), (byte) value};
    }

    /**
     * Converts a signed short to a byte array.
     * <p/>
     * The byte order is big-endian.
     *
     * @param value the short value.
     * @return an array with two bytes.
     */
    public static byte[] getBytes(short value) {
        return new byte[]{(byte) (value >> 8), (byte) value};
    }

    /**
     * Converts the lowest five bytes of an unsigned long to a byte array.
     * <p/>
     * The byte order is big-endian.
     *
     * @param value the long value, must not be negative.
     * @return an array with five bytes.
     */
    public static byte[] getFiveBytes(long value) {
        if (value < 0) {
            throw new IllegalArgumentException("negative value not allowed: " + value);
        } else if (value > 1099511627775L) {
            throw new IllegalArgumentException("value out of range: " + value);
        }
        return new byte[]{(byte) (value >> 32), (byte) (value >> 24), (byte) (value >> 16), (byte) (value >> 8),
                (byte) value};
    }

    /**
     * Converts a signed int to a variable length byte array.
     * <p/>
     * The first bit is for continuation info, the other six (last byte) or seven (all other bytes) bits for data. The
     * second bit in the last byte indicates the sign of the number.
     *
     * @param value the int value.
     * @return an array with 1-5 bytes.
     */
    public static byte[] getVariableByteSigned(int value) {
        long absValue = Math.abs((long) value);
        if (absValue < 64) { // 2^6
            // encode the number in a single byte
            if (value < 0) {
                return new byte[]{(byte) (absValue | 0x40)};
            }
            return new byte[]{(byte) absValue};
        } else if (absValue < 8192) { // 2^13
            // encode the number in two bytes
            if (value < 0) {
                return new byte[]{(byte) (absValue | 0x80), (byte) ((absValue >> 7) | 0x40)};
            }
            return new byte[]{(byte) (absValue | 0x80), (byte) (absValue >> 7)};
        } else if (absValue < 1048576) { // 2^20
            // encode the number in three bytes
            if (value < 0) {
                return new byte[]{(byte) (absValue | 0x80), (byte) ((absValue >> 7) | 0x80),
                        (byte) ((absValue >> 14) | 0x40)};
            }
            return new byte[]{(byte) (absValue | 0x80), (byte) ((absValue >> 7) | 0x80), (byte) (absValue >> 14)};
        } else if (absValue < 134217728) { // 2^27
            // encode the number in four bytes
            if (value < 0) {
                return new byte[]{(byte) (absValue | 0x80), (byte) ((absValue >> 7) | 0x80),
                        (byte) ((absValue >> 14) | 0x80), (byte) ((absValue >> 21) | 0x40)};
            }
            return new byte[]{(byte) (absValue | 0x80), (byte) ((absValue >> 7) | 0x80),
                    (byte) ((absValue >> 14) | 0x80), (byte) (absValue >> 21)};
        } else {
            // encode the number in five bytes
            if (value < 0) {
                return new byte[]{(byte) (absValue | 0x80), (byte) ((absValue >> 7) | 0x80),
                        (byte) ((absValue >> 14) | 0x80), (byte) ((absValue >> 21) | 0x80),
                        (byte) ((absValue >> 28) | 0x40)};
            }
            return new byte[]{(byte) (absValue | 0x80), (byte) ((absValue >> 7) | 0x80),
                    (byte) ((absValue >> 14) | 0x80), (byte) ((absValue >> 21) | 0x80), (byte) (absValue >> 28)};
        }
    }

    /**
     * Converts an unsigned int to a variable length byte array.
     * <p/>
     * The first bit is for continuation info, the other seven bits for data.
     *
     * @param value the int value, must not be negative.
     * @return an array with 1-5 bytes.
     */
    public static byte[] getVariableByteUnsigned(int value) {
        if (value < 0) {
            throw new InvalidParameterException("negative value not allowed: " + value);
        } else if (value < 128) { // 2^7
            // encode the number in a single byte
            return new byte[]{(byte) value};
        } else if (value < 16384) { // 2^14
            // encode the number in two bytes
            return new byte[]{(byte) (value | 0x80), (byte) (value >> 7)};
        } else if (value < 2097152) { // 2^21
            // encode the number in three bytes
            return new byte[]{(byte) (value | 0x80), (byte) ((value >> 7) | 0x80), (byte) (value >> 14)};
        } else if (value < 268435456) { // 2^28
            // encode the number in four bytes
            return new byte[]{(byte) (value | 0x80), (byte) ((value >> 7) | 0x80), (byte) ((value >> 14) | 0x80),
                    (byte) (value >> 21)};
        } else {
            // encode the number in five bytes
            return new byte[]{(byte) (value | 0x80), (byte) ((value >> 7) | 0x80), (byte) ((value >> 14) | 0x80),
                    (byte) ((value >> 21) | 0x80), (byte) (value >> 28)};
        }
    }

    private Serializer() {
        throw new IllegalStateException();
    }
}
