/* Copyright (c) 2010 Daniel Doubrovkine, All Rights Reserved
 *
 * This library 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 2.1 of the License, or (at your option) any later version.
 *
 * This library 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.
 */
package com.sun.jna.platform.win32;

import java.security.SecureRandom;
import java.util.Arrays;
import java.util.List;

import com.sun.jna.Pointer;
import com.sun.jna.Structure;

// TODO: Auto-generated Javadoc
/**
 * Ported from Guid.h. Microsoft Windows SDK 6.0A.
 * 
 * @author dblock[at]dblock.org
 */
public interface Guid {

    /** The Constant IID_NULL. */
    public final static IID IID_NULL = new IID();

    /**
     * The Class GUID.
     *
     * @author Tobias Wolf, wolf.tobias@gmx.net
     */
    public static class GUID extends Structure {

    	public static class ByValue extends GUID implements Structure.ByValue {
    		public ByValue() {}
            public ByValue(GUID guid) {
                super(guid.getPointer());

                Data1 = guid.Data1;
                Data2 = guid.Data2;
                Data3 = guid.Data3;
                Data4 = guid.Data4;
            }
            public ByValue(Pointer memory) {
                super(memory);
            }
    	}
    	
        /**
         * The Class ByReference.
         *
         * @author Tobias Wolf, wolf.tobias@gmx.net
         */
        public static class ByReference extends GUID implements
                Structure.ByReference {

            /**
             * Instantiates a new by reference.
             */
            public ByReference() {
            }

            /**
             * Instantiates a new by reference.
             * 
             * @param guid
             *            the guid
             */
            public ByReference(GUID guid) {
                super(guid.getPointer());

                Data1 = guid.Data1;
                Data2 = guid.Data2;
                Data3 = guid.Data3;
                Data4 = guid.Data4;
            }

            /**
             * Instantiates a new by reference.
             * 
             * @param memory
             *            the memory
             */
            public ByReference(Pointer memory) {
                super(memory);
            }
        }

        /** The Data1. */
        public int Data1;

        /** The Data2. */
        public short Data2;

        /** The Data3. */
        public short Data3;

        /** The Data4. */
        public byte[] Data4 = new byte[8];

        /**
         * Instantiates a new guid.
         */
        public GUID() {
        }

        /**
         * Instantiates a new guid.
         * 
         * @param guid
         *            the guid
         */
        public GUID(GUID guid) {
            this.Data1 = guid.Data1;
            this.Data2 = guid.Data2;
            this.Data3 = guid.Data3;
            this.Data4 = guid.Data4;

            this.writeFieldsToMemory();
        }

        /**
         * Instantiates a new guid.
         * 
         * @param guid
         *            the guid
         */
        public GUID(String guid) {
            this(fromString(guid));
        }

        /**
         * Instantiates a new guid.
         * 
         * @param data
         *            the data
         */
        public GUID(byte[] data) {
            this(fromBinary(data));
        }

        /**
         * Instantiates a new guid.
         * 
         * @param memory
         *            the memory
         */
        public GUID(Pointer memory) {
            super(memory);
            read();
        }

        /**
         * From binary.
         * 
         * @param data
         *            the data
         * @return the guid
         */
        public static GUID fromBinary(byte[] data) {
            if (data.length != 16) {
                throw new IllegalArgumentException("Invalid data length: "
                        + data.length);
            }

            GUID newGuid = new GUID();
            long data1Temp = data[0] & 0xff;
            data1Temp <<= 8;
            data1Temp |= data[1] & 0xff;
            data1Temp <<= 8;
            data1Temp |= data[2] & 0xff;
            data1Temp <<= 8;
            data1Temp |= data[3] & 0xff;
            newGuid.Data1 = (int) data1Temp;

            int data2Temp = data[4] & 0xff;
            data2Temp <<= 8;
            data2Temp |= data[5] & 0xff;
            newGuid.Data2 = (short) data2Temp;

            int data3Temp = data[6] & 0xff;
            data3Temp <<= 8;
            data3Temp |= data[7] & 0xff;
            newGuid.Data3 = (short) data3Temp;

            newGuid.Data4[0] = data[8];
            newGuid.Data4[1] = data[9];
            newGuid.Data4[2] = data[10];
            newGuid.Data4[3] = data[11];
            newGuid.Data4[4] = data[12];
            newGuid.Data4[5] = data[13];
            newGuid.Data4[6] = data[14];
            newGuid.Data4[7] = data[15];

            newGuid.writeFieldsToMemory();

            return newGuid;
        }

        /**
         * From string.
         * 
         * @param guid
         *            the guid
         * @return the guid
         */
        public static GUID fromString(String guid) {
            int y = 0;
            char[] _cnewguid = new char[32];
            char[] _cguid = guid.toCharArray();
            byte[] bdata = new byte[16];
            GUID newGuid = new GUID();

            // we not accept a string longer than 38 chars
            if (guid.length() > 38) {
                throw new IllegalArgumentException("Invalid guid length: "
                        + guid.length());
            }

            // remove '{', '}' and '-' from guid string
            for (int i = 0; i < _cguid.length; i++) {
                if ((_cguid[i] != '{') && (_cguid[i] != '-')
                        && (_cguid[i] != '}'))
                    _cnewguid[y++] = _cguid[i];
            }

            // convert char to byte
            for (int i = 0; i < 32; i += 2) {
                bdata[i / 2] = (byte) ((Character.digit(_cnewguid[i], 16) << 4)
                        + Character.digit(_cnewguid[i + 1], 16) & 0xff);
            }

            if (bdata.length != 16) {
                throw new IllegalArgumentException("Invalid data length: "
                        + bdata.length);
            }

            long data1Temp = bdata[0] & 0xff;
            data1Temp <<= 8;
            data1Temp |= bdata[1] & 0xff;
            data1Temp <<= 8;
            data1Temp |= bdata[2] & 0xff;
            data1Temp <<= 8;
            data1Temp |= bdata[3] & 0xff;
            newGuid.Data1 = (int) data1Temp;

            int data2Temp = bdata[4] & 0xff;
            data2Temp <<= 8;
            data2Temp |= bdata[5] & 0xff;
            newGuid.Data2 = (short) data2Temp;

            int data3Temp = bdata[6] & 0xff;
            data3Temp <<= 8;
            data3Temp |= bdata[7] & 0xff;
            newGuid.Data3 = (short) data3Temp;

            newGuid.Data4[0] = bdata[8];
            newGuid.Data4[1] = bdata[9];
            newGuid.Data4[2] = bdata[10];
            newGuid.Data4[3] = bdata[11];
            newGuid.Data4[4] = bdata[12];
            newGuid.Data4[5] = bdata[13];
            newGuid.Data4[6] = bdata[14];
            newGuid.Data4[7] = bdata[15];

            newGuid.writeFieldsToMemory();

            return newGuid;
        }

        /**
         * Generates a new guid. Code taken from the standard jdk implementation
         * (see UUID class).
         * 
         * @return the guid
         */
        public static GUID newGuid() {
            SecureRandom ng = new SecureRandom();
            byte[] randomBytes = new byte[16];

            ng.nextBytes(randomBytes);
            randomBytes[6] &= 0x0f;
            randomBytes[6] |= 0x40;
            randomBytes[8] &= 0x3f;
            randomBytes[8] |= 0x80;

            return new GUID(randomBytes);
        }

        /**
         * To byte array.
         * 
         * @return the byte[]
         */
        public byte[] toByteArray() {
            byte[] guid = new byte[16];

            byte[] bytes1 = new byte[4];
            bytes1[0] = (byte) (Data1 >> 24);
            bytes1[1] = (byte) (Data1 >> 16);
            bytes1[2] = (byte) (Data1 >> 8);
            bytes1[3] = (byte) (Data1 >> 0);

            byte[] bytes2 = new byte[4];
            bytes2[0] = (byte) (Data2 >> 24);
            bytes2[1] = (byte) (Data2 >> 16);
            bytes2[2] = (byte) (Data2 >> 8);
            bytes2[3] = (byte) (Data2 >> 0);

            byte[] bytes3 = new byte[4];
            bytes3[0] = (byte) (Data3 >> 24);
            bytes3[1] = (byte) (Data3 >> 16);
            bytes3[2] = (byte) (Data3 >> 8);
            bytes3[3] = (byte) (Data3 >> 0);

            System.arraycopy(bytes1, 0, guid, 0, 4);
            System.arraycopy(bytes2, 2, guid, 4, 2);
            System.arraycopy(bytes3, 2, guid, 6, 2);
            System.arraycopy(Data4, 0, guid, 8, 8);

            return guid;
        }

        /**
         * The value of this Guid, formatted as follows:
         * xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.
         * 
         * @return the string
         */
        public String toGuidString() {
            final String HEXES = "0123456789ABCDEF";
            byte[] bGuid = toByteArray();

            final StringBuilder hexStr = new StringBuilder(2 * bGuid.length);
            hexStr.append("{");

            for (int i = 0; i < bGuid.length; i++) {
                char ch1 = HEXES.charAt((bGuid[i] & 0xF0) >> 4);
                char ch2 = HEXES.charAt(bGuid[i] & 0x0F);
                hexStr.append(ch1).append(ch2);

                if ((i == 3) || (i == 5) || (i == 7) || (i == 9))
                    hexStr.append("-");
            }

            hexStr.append("}");
            return hexStr.toString();
        }

        /**
         * Write fields to backing memory.
         */
        protected void writeFieldsToMemory() {
            this.writeField("Data1");
            this.writeField("Data2");
            this.writeField("Data3");
            this.writeField("Data4");
        }

        /*
         * (non-Javadoc)
         * 
         * @see com.sun.jna.Structure#getFieldOrder()
         */
        protected List getFieldOrder() {
            return Arrays.asList(new String[] { "Data1", "Data2", "Data3",
                    "Data4" });
        }
    }

    /**
     * The Class CLSID.
     *
     * @author Tobias Wolf, wolf.tobias@gmx.net
     */
    public static class CLSID extends GUID {

        /**
         * The Class ByReference.
         *
         * @author Tobias Wolf, wolf.tobias@gmx.net
         */
        public static class ByReference extends GUID {

            /**
             * Instantiates a new by reference.
             */
            public ByReference() {
            }

            /**
             * Instantiates a new by reference.
             * 
             * @param guid
             *            the guid
             */
            public ByReference(GUID guid) {
                super(guid);
            }
             
            /**
             * Instantiates a new by reference.
             * 
             * @param memory
             *            the memory
             */
            public ByReference(Pointer memory) {

            }
        }

        /**
         * Instantiates a new clsid.
         */
        public CLSID() {
        }
        
        /**
         * Instantiates a new clsid.
         *
         * @param guid the guid
         */
        public CLSID(String guid) {
            super(guid);
        }
        
        /**
         * Instantiates a new clsid.
         *
         * @param guid the guid
         */
        public CLSID(GUID guid) {
            super(guid);
        }
    }

    /**
     * The Class REFIID.
     *
     * @author Tobias Wolf, wolf.tobias@gmx.net
     */
    public class REFIID extends IID {

        /**
         * Instantiates a new refiid.
         */
        public REFIID() {
            // TODO Auto-generated constructor stub
        }

        /**
         * Instantiates a new refiid.
         * 
         * @param memory
         *            the memory
         */
        public REFIID(Pointer memory) {
            super(memory);
            // TODO Auto-generated constructor stub
        }

        /**
         * Instantiates a new refiid.
         * 
         * @param data
         *            the data
         */
        public REFIID(byte[] data) {
            super(data);
            // TODO Auto-generated constructor stub
        }

    }

    /**
     * The Class IID.
     *
     * @author Tobias Wolf, wolf.tobias@gmx.net
     */
    public class IID extends GUID {

        /**
         * Instantiates a new iid.
         */
        public IID() {
            // TODO Auto-generated constructor stub
        }

        /**
         * Instantiates a new iid.
         * 
         * @param memory
         *            the memory
         */
        public IID(Pointer memory) {
            super(memory);
            // TODO Auto-generated constructor stub
        }

        /**
         * Instantiates a new iid.
         *
         * @param iid the iid
         */
        public IID(String iid) {
            super(iid);
            // TODO Auto-generated constructor stub
        }

        /**
         * Instantiates a new iid.
         * 
         * @param data
         *            the data
         */
        public IID(byte[] data) {
            super(data);
            // TODO Auto-generated constructor stub
        }
    }
}