/*******************************************************************************
 * Copyright (c) 2014 Ericsson
 *
 * 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:
 *   Vincent Perot - Initial API and implementation
 *******************************************************************************/

package org.eclipse.tracecompass.internal.pcap.core.util;

import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.eclipse.tracecompass.common.core.NonNullUtils;
import org.eclipse.tracecompass.internal.pcap.core.protocol.ethernet2.EthernetIIValues;

/**
 * Class for helping with the conversion of data.
 *
 * @author Vincent Perot
 */
public final class ConversionHelper {

    private static final char[] HEX_ARRAY = checkNotNull("0123456789abcdef".toCharArray()); //$NON-NLS-1$
    private static final String EMPTY_STRING = ""; //$NON-NLS-1$
    private static final String DEFAULT_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss.SSS"; //$NON-NLS-1$
    private static final DateFormat DATE_FORMATTER = new SimpleDateFormat(DEFAULT_TIME_PATTERN);

    private ConversionHelper() {
    }

    /**
     * Generate an integer from an unsigned byte.
     *
     * @param n
     *            the unsigned byte.
     * @return the integer representing the unsigned value.
     */
    public static int unsignedByteToInt(byte n) {
        return n & 0x000000FF;
    }

    /**
     * Generate an integer from an unsigned short.
     *
     * @param n
     *            the unsigned short.
     * @return the integer representing the unsigned value.
     */
    public static int unsignedShortToInt(short n) {
        return n & 0x0000FFFF;
    }

    /**
     * Generate a long from an unsigned integer.
     *
     * @param n
     *            the unsigned integer.
     * @return the long representing the unsigned value.
     */
    public static long unsignedIntToLong(int n) {
        return n & 0x00000000FFFFFFFFL;
    }

    /**
     * Generate an hex number from a byte array.
     *
     * @param bytes
     *            The array of bytes.
     * @param spaced
     *            Whether there must be a space between each byte or not.
     * @return the hex as a string.
     */
    public static String bytesToHex(byte[] bytes, boolean spaced) {
        // No need to check for character encoding since bytes represents a
        // number.

        if (bytes.length == 0) {
            return EMPTY_STRING;
        }

        char[] hexChars = spaced ? new char[bytes.length * 3 - 1] : new char[bytes.length * 2];
        int delta = spaced ? 3 : 2;
        char separator = ' ';

        for (int j = 0; j < bytes.length; j++) {

            int v = bytes[j] & 0xFF;
            hexChars[j * delta] = HEX_ARRAY[v >>> 4];
            hexChars[j * delta + 1] = HEX_ARRAY[v & 0x0F];

            if (spaced && (j != bytes.length - 1)) {
                hexChars[j * delta + 2] = separator;
            }
        }
        return new String(hexChars);
    }

    // TODO Add little endian support
    /**
     * Generate a string representing the MAC address.
     *
     * @param mac
     *            The MAC address as a byte array.
     * @return The string representing the MAC address.
     */
    public static String toMacAddress(byte[] mac) {

        if (mac.length != EthernetIIValues.MAC_ADDRESS_SIZE) {
            throw new IllegalArgumentException();
        }
        char separator = ':';
        return String.format("%02x", mac[0]) + separator + //$NON-NLS-1$
                String.format("%02x", mac[1]) + separator + //$NON-NLS-1$
                String.format("%02x", mac[2]) + separator + //$NON-NLS-1$
                String.format("%02x", mac[3]) + separator + //$NON-NLS-1$
                String.format("%02x", mac[4]) + separator + //$NON-NLS-1$
                String.format("%02x", mac[5]); //$NON-NLS-1$

    }

    // TODO support non GMT time.

    /**
     * Convert a timestamp into a date.
     *
     * @param ts
     *            The timestamp. It represents the time since Epoch in
     *            microseconds.
     * @param scale
     *            The scale of the timestamp.
     * @return The date as a string.
     */
    public static String toGMTTime(long ts, PcapTimestampScale scale) {
        long timestamp;
        switch (scale) {
        case MICROSECOND:
            timestamp = ts * 1000;
            break;
        case NANOSECOND:
            timestamp = ts;
            break;
        default:
            throw new IllegalArgumentException("The timestamp precision is not valid!"); //$NON-NLS-1$
        }
        return format(timestamp);
    }

    /**
     * Format the timestamp to a string.
     *
     * @param value
     *            the timestamp value to format (in ns)
     * @return the formatted timestamp
     */
    private static String format(long value) {
        // Split the timestamp value into its sub-components
        long date = value / 1000000; // milliseconds since epoch
        long cs = Math.abs((value % 1000000) / 1000); // microseconds
        long ns = Math.abs(value % 1000); // nanoseconds

        Date dateObject = new Date(date);

        StringBuilder sb = new StringBuilder(DATE_FORMATTER.format(dateObject));
        sb.append('.')
                .append(String.format("%03d", cs)) //$NON-NLS-1$
                .append('.')
                .append(String.format("%03d", ns)); //$NON-NLS-1$

        return NonNullUtils.nullToEmptyString(sb);

    }

}
