package utility;

public class BinaryDisplayConverter {

        // Use Hexidecimal representation
        public static final int HEX = 16;
        //Use decimal representation.
        public static final int DECIMAL = 10;
        //Use Octal representation.
        public static final int OCTAL = 8;
        //Use Binary representation.
        public static final int BINARY = 2;

        static ConversionConstants hex = new ConversionConstants(2, 16);
        static ConversionConstants decimal = new ConversionConstants(3, 10);
        private static ConversionConstants octal = new ConversionConstants(3, 8);
        private static ConversionConstants binary = new ConversionConstants(8, 2);

        //Conversion Constants
        static class ConversionConstants {
            int width;    // number of chars used to represent byte
            int radix;    // the base radix

            ConversionConstants(int w, int r) {
                width = w;
                radix = r;
            }
        }

        //List of characters considered "printable".
        private static String printable
                = "0123456789abcdefghijklmnopqrstuvwxyz"
                        +  "ABCDEFGHIJKLMNOPQRSTUVWXYZ`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?";

        /**
         * Do not allow any instances to be created.
         */
        private BinaryDisplayConverter() {
        }

        /**
         * Convert from an array of Bytes into a string.
         */
        public static String convertToString(Byte[] data, int base, boolean showAscii) {

            // handle null
            if (data == null)
                return null;

            StringBuffer buf = new StringBuffer();

            ConversionConstants convConst = getConstants(base);

            // Convert each byte and put into string buffer
            for (int i = 0; i < data.length; i++) {
                int value = data[i].byteValue();
                String s = null;

                // if user wants to see ASCII chars as characters,
                // see if this is one that should be displayed that way
                if (showAscii) {
                    if (printable.indexOf((char) value) > -1) {
                        s = Character.valueOf((char) value) +
                                "          ".substring(10 - (convConst.width - 1));
                    }
                }

                // if use is not looking for ASCII chars, or if this one is one that
                // is not printable, then convert it into numeric form
                if (s == null) {
                    switch (base) {
                        case DECIMAL:
                            // convert signed to unsigned
                            if (value < 0)
                                value = 256 + value;
                            s = Integer.toString(value);
                            break;
                        case OCTAL:
                            s = Integer.toOctalString(value);
                            break;
                        case BINARY:
                            s = Integer.toBinaryString(value);
                            break;
                        case HEX:    // fall through to default
                        default:
                            s = Integer.toHexString(value);
                    }
                    // some formats (e.g. hex & octal) extend a negative number to multiple places
                    // (e.g. FC becomes FFFC), so chop off extra stuff in front
                    if (s.length() > convConst.width)
                        s = s.substring(s.length() - convConst.width);

                    // front pad with zeros and add to output
                    if (s.length() < convConst.width)
                        buf.append("00000000".substring(8 - (convConst.width - s.length())));
                }
                buf.append(s);
                buf.append("");    // always add spaces at end for consistancy
            }
            return buf.toString();
        }

        /**
         * Convert a string into Bytes.  The string is assumed to be in
         * the form generated by the convertToString function, with each
         * byte's data space separated from each other byte's.
         */
        public static Byte[] convertToBytes(String data, int base, boolean showAscii)
                throws NumberFormatException {

            ConversionConstants convConst = getConstants(base);

            if (data == null)
                return null;

            if (data.length() == 0)
                return new Byte[0];

            if (data.equals("<null>"))
                return null;

            int stringIndex = 0;
            int byteIndex = 0;
            Byte[] bytes = new Byte[(data.length() + 2) / (convConst.width + 2)];
            while (stringIndex < data.length()) {
                // get the text to be converted
                String s = data.substring(stringIndex, stringIndex + convConst.width);

                // handle ASCII chars
                // Irrespective of the radix, the second byte will always
                // be a space when the data is displayed as a single ASCII character.
                if (showAscii && s.charAt(1) == ' ') {
                    // convert the char into its numeric value
                    bytes[byteIndex++] = Byte.valueOf((byte) s.charAt(0));
                } else {

                    // The following ugly conversion from text to Byte is necessary because
                    // the Byte class is inconsistant.  When asked to output as Hex, it does
                    // so as an UNSIGNED byte, but when asked to read back the same thing
                    // using the Hex radix, it insists that the input must be SIGNED.
                    // To get around this, we up-size the conversion to Integer, then
                    // truncate that to a byte, and finally convert the byte to a Byte.  Yech.
                    bytes[byteIndex++] = Byte.valueOf(
                            (byte) (Integer.valueOf(s, convConst.radix)).intValue());
                }

                stringIndex += convConst.width + 2;
            }

            return bytes;
        }

        /**
         * Get the constants to use for the given base.
         */
        private static ConversionConstants getConstants(int base) {
            if (base == HEX) return hex;
            if (base == DECIMAL) return decimal;
            if (base == OCTAL) return octal;
            if (base == BINARY) return binary;
            return hex;    // default to hex if unknown base passed in
        }

}
