/*
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.text;

import java.io.IOException;
import java.io.UncheckedIOException;

import lombok.experimental.UtilityClass;

/**
 *
 * @author ranides
 */
@UtilityClass
public final class FormatHex {
    
    private static final char[] HC_TABLE = {
        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
    };

    private static final byte[] CH_TABLE = new byte[128];

    static {
        CH_TABLE['0'] = 0;
        CH_TABLE['1'] = 1;
        CH_TABLE['2'] = 2;
        CH_TABLE['3'] = 3;
        CH_TABLE['4'] = 4;
        CH_TABLE['5'] = 5;
        CH_TABLE['6'] = 6;
        CH_TABLE['7'] = 7;
        CH_TABLE['8'] = 8;
        CH_TABLE['9'] = 9;
        CH_TABLE['A'] = 10;
        CH_TABLE['a'] = 10;
        CH_TABLE['B'] = 11;
        CH_TABLE['b'] = 11;
        CH_TABLE['C'] = 12;
        CH_TABLE['c'] = 12;
        CH_TABLE['D'] = 13;
        CH_TABLE['d'] = 13;
        CH_TABLE['E'] = 14;
        CH_TABLE['e'] = 14;
        CH_TABLE['F'] = 15;
        CH_TABLE['f'] = 15;
    }

    /**
     * Encodes bytes into list of hexadecimal numbers separated by spaces.
     *
     * @param raw raw
     * @return String
     */
    public static String format(byte[] raw) {
        return format(new StrBuilder(3*raw.length), raw).toString();
    }

    /**
     * Encodes bytes into list of hexadecimal numbers separated by spaces.
     * Generated result is inserted into "target" buffer.
     *
     * @param target target
     * @param raw raw
     * @param <T> T
     * @return target
     */
    public static <T extends Appendable> T format(T target, byte[] raw) {
        try {
            int n= raw.length;
            if(n > 0) {
                appendByte(target, raw[0]);
            }
            for(int i=1; i<n; i++) {
                target.append(' ');
                appendByte(target, raw[i]);
            }
            return target;
        } catch (IOException cause) {
            throw new UncheckedIOException(cause);
        }
    }

    /**
     * Encodes byte into 2-digit hexadecimal number.
     * Generated result is inserted into "target" buffer.
     *
     * @param target target
     * @param value value
     */
    public static void appendByte(Appendable target, byte value) {
        try {
            int ivalue = value & 0xFF;
            target.append(HC_TABLE[ivalue >>> 4]);
            target.append(HC_TABLE[ivalue & 0xF]);
        } catch (IOException cause) {
            throw new UncheckedIOException(cause);
        }
    }

    /**
     * Encodes char into 4-digit hexadecimal number.
     * Generated result is inserted into "target" buffer.
     *
     * @param target target
     * @param value value
     */
    public static void appendChar(Appendable target, char value) {
        try {
            int ivalue = value & 0xFFFF;
            target.append(HC_TABLE[(ivalue >>> 12) & 0xF]);
            target.append(HC_TABLE[(ivalue >>> 8) & 0xF]);
            target.append(HC_TABLE[(ivalue >>> 4) & 0xF]);
            target.append(HC_TABLE[(ivalue & 0xF)]);
        } catch (IOException cause) {
            throw new UncheckedIOException(cause);
        }
    }

    /**
     * Takes 4 characters from "text" and converts extracted 4-digit hexadecimal number into char.
     *
     * Starts reading characters at "offset".
     *
     * @param text text
     * @param offset offset
     * @return char
     */
    public static char parseChar(CharSequence text, int offset) {
        byte a = CH_TABLE[text.charAt(offset++)];
        byte b = CH_TABLE[text.charAt(offset++)];
        byte c = CH_TABLE[text.charAt(offset++)];
        byte d = CH_TABLE[text.charAt(offset++)];

        return (char) ((a << 12) | (b << 8) | (c << 4) | (d));
    }

    /**
     * Converts list of 2-digit hexadecimal numbers separated by spaces into bytes.
     *
     * @param text text
     * @return bytes
     */
    public static byte[] parse(CharSequence text) {
        int bytes = (text.length()+1) / 3;
        byte[] target = new byte[bytes];
        int c = 0;
        int i = 0;
        int n=text.length()-2;
        while(i<n) {
            byte a = CH_TABLE[text.charAt(i++)];
            byte b = CH_TABLE[text.charAt(i++)];
            target[c++] = (byte)( (a<<4) | (b&0xF) );
            i++;
        }

        target[c] = (byte)( (CH_TABLE[text.charAt(i)] <<4) | (CH_TABLE[text.charAt(i+1)] &0xF) );
        return target;
    }

}
