/*
 * [TestHex.java]
 *
 * Summary: Demonstrate how to handle hex strings.
 *
 * Copyright: (c) 2009-2017 Roedy Green, Canadian Mind Products, http://mindprod.com
 *
 * Licence: This software may be copied and used freely for any purpose but military.
 *          http://mindprod.com/contact/nonmil.html
 *
 * Requires: JDK 1.8+
 *
 * Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/
 *
 * Version History:
 *  1.0 2009-06-05 initial version.
 */
package com.mindprod.example;

import com.mindprod.common18.ST;

import static java.lang.System.*;

/**
 * Demonstrate how to handle hex strings.
 *
 * @author Roedy Green, Canadian Mind Products
 * @version 1.0 2009-06-05 initial version.
 * @since 2009-06-05
 */
public final class TestHex
    {
    /**
     * precomputed translate table for chars 0..'f'
     */
    private static final byte[] correspondingNibble = new byte[ 'f' + 1 ];

    static
        {
        // only 0..9 A..F a..f have meaning. rest are errors.
        for ( int i = 0; i <= 'f'; i++ )
            {
            correspondingNibble[ i ] = -1;
            }
        for ( int i = '0'; i <= '9'; i++ )
            {
            correspondingNibble[ i ] = ( byte ) ( i - '0' );
            }
        for ( int i = 'A'; i <= 'F'; i++ )
            {
            correspondingNibble[ i ] = ( byte ) ( i - 'A' + 10 );
            }
        for ( int i = 'a'; i <= 'f'; i++ )
            {
            correspondingNibble[ i ] = ( byte ) ( i - 'a' + 10 );
            }
        }

    /**
     * convert  a single char to corresponding nibble using a precalculated array.
     * Based on code by:
     * Brian Marquis
     * Orion Group Software Engineers http://www.ogse.com
     *
     * @param c char to convert. must be 0-9 a-f A-F, no
     *          spaces, plus or minus signs.
     *
     * @return corresponding integer  0..15
     * @throws IllegalArgumentException on invalid c.
     */
    private static int charToNibble( char c )
        {
        if ( c > 'f' )
            {
            throw new IllegalArgumentException( "Invalid hex character: " + c );
            }
        int nibble = correspondingNibble[ c ];
        if ( nibble < 0 )
            {
            throw new IllegalArgumentException( "Invalid hex character: " + c );
            }
        return nibble;
        }

    /**
     * Convert a hex string to an unsigned byte array.
     * Permits upper or lower case hex.
     *
     * @param s String must have even number of characters.
     *          and be formed only of digits 0-9 A-F or
     *          a-f. No spaces, minus or plus signs.
     *
     * @return corresponding unsigned byte array. see http://mindprod.com/jgloss/unsigned.html
     */
    private static byte[] fromHexString( String s )
        {
        int stringLength = s.length();
        if ( ( stringLength & 0x1 ) != 0 )
            {
            throw new IllegalArgumentException( "fromHexString requires an even number of hex characters" );
            }
        byte[] bytes = new byte[ stringLength / 2 ];
        for ( int i = 0, j = 0; i < stringLength; i += 2, j++ )
            {
            int high = charToNibble( s.charAt( i ) );
            int low = charToNibble( s.charAt( i + 1 ) );
            // You can store either unsigned 0..255 or signed -128..127 bytes in a byte type.
            bytes[ j ] = ( byte ) ( ( high << 4 ) | low );
            }
        return bytes;
        }

    /**
     * code not used, for explanation only.
     * convert a single char to corresponding nibble.
     * Slow version, easier to understand.
     *
     * @param c char to convert. must be 0-9 a-f A-F, no
     *          spaces, plus or minus signs.
     *
     * @return corresponding integer
     */
    private static int slowCharToNibble( char c )
        {
        if ( '0' <= c && c <= '9' )
            {
            return c - '0';
            }
        else if ( 'a' <= c && c <= 'f' )
            {
            return c - 'a' + 0xa;
            }
        else if ( 'A' <= c && c <= 'F' )
            {
            return c - 'A' + 0xa;
            }
        else
            {
            throw new IllegalArgumentException( "Invalid hex character: " + c );
            }
        }

    /**
     * Test harness
     *
     * @param args not used
     */
    public static void main( String[] args )
        {
        String hexString = "9000003D050000000000123456789ABCDEFabcde6f00000000000000";
        out.println( hexString );
        // convert hex string to an array of bytes
        byte[] bytes = fromHexString( hexString );
        out.println( bytes.length + " bytes long" );
        for ( byte b : bytes )
            {
            out.print( ST.toLZHexString( b & 0xff, 2 ) );
            out.print( ' ' );
            }
        out.println();
        }
    }