////////////////////////////////////////////////////////////////////////////////
//
//  Licensed to the Apache Software Foundation (ASF) under one or more
//  contributor license agreements.  See the NOTICE file distributed with
//  this work for additional information regarding copyright ownership.
//  The ASF licenses this file to You under the Apache License, Version 2.0
//  (the "License"); you may not use this file except in compliance with
//  the License.  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////

package org.apache.royale.utils
{

/**
 *  The UIDUtil class is an all-static class
 *  with methods for working with UIDs (unique identifiers) within Royale.
 *  You do not create instances of UIDUtil;
 *  instead you simply call static methods such as the
 *  <code>UIDUtil.createUID()</code> method.
 * 
 *  <p><b>Note</b>: If you have a dynamic object that has no [Bindable] properties 
 *  (which force the object to implement the IUID interface), Royale adds an 
 *  internal property that contains a UID to the object. 
 *  To avoid having this field 
 *  in your dynamic object, make it [Bindable], implement the IUID interface
 *  in the object class, or set a <coded>uid</coded> property with a value.</p>
 *  
 *  @langversion 3.0
 *  @playerversion Flash 9
 *  @playerversion AIR 1.1
 *  @productversion Royale 1.0.0
 *  @productversion Royale 0.0
 */
public class UIDUtil
{

    //--------------------------------------------------------------------------
    //
    //  Class constants
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     *  Char codes for 0123456789ABCDEF
     */
	private static const ALPHA_CHAR_CODES:Array = [48, 49, 50, 51, 52, 53, 54, 
		55, 56, 57, 65, 66, 67, 68, 69, 70];

    private static const DASH:int = 45;       // dash ascii
    
    private static var _UIDBuffer:BinaryData;
    private static function get UIDBuffer():BinaryData{
        if(_UIDBuffer == null)
            _UIDBuffer = new BinaryData();       // static ByteArray used for UID generation to save memory allocation cost
        
        return _UIDBuffer;
    }


    //--------------------------------------------------------------------------
    //
    //  Class methods
    //
    //--------------------------------------------------------------------------

    /**
     *  Generates a UID (unique identifier) based on ActionScript's
     *  pseudo-random number generator and the current time.
     *
     *  <p>The UID has the form
     *  <code>"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"</code>
     *  where X is a hexadecimal digit (0-9, A-F).</p>
     *
     *  <p>This UID will not be truly globally unique; but it is the best
     *  we can do without player support for UID generation.</p>
     *
     *  @return The newly-generated UID.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Royale 1.0.0
	 *  @productversion Royale 0.0
     */
	public static function createUID():String
    {
		var i:int;
		var j:int;
		
        UIDBuffer.position = 0;

        for (i = 0; i < 8; i++)
        {
            UIDBuffer.writeByte(ALPHA_CHAR_CODES[int(Math.random() * 16)]);
        }

        for (i = 0; i < 3; i++)
        {
            UIDBuffer.writeByte(DASH);
            for (j = 0; j < 4; j++)
            {
                UIDBuffer.writeByte(ALPHA_CHAR_CODES[int(Math.random() * 16)]);
            }
        }

        UIDBuffer.writeByte(DASH);

        COMPILE::JS
        {
            var time:uint = Math.floor(new Date().getTime()/1000); // extract last 8 digits
        }
        COMPILE::SWF
        {
            var time:uint = new Date().getTime(); // extract last 8 digits
        }
        var timeString:String = time.toString(16).toUpperCase();
        // 0xFFFFFFFF milliseconds ~= 3 days, so timeString may have between 1 and 8 digits, hence we need to pad with 0s to 8 digits
        for (i = 8; i > timeString.length; i--)
            UIDBuffer.writeByte(48);
        UIDBuffer.writeUTFBytes(timeString);

        for (i = 0; i < 4; i++)
        {
            UIDBuffer.writeByte(ALPHA_CHAR_CODES[int(Math.random() * 16)]);
        }
        UIDBuffer.position = 0;

        return UIDBuffer.toString();//UIDBuffer.readUTFBytes(UIDBuffer.length);
    }

    /**
     * Converts a 128-bit UID encoded as a ByteArray to a String representation.
     * The format matches that generated by createUID. If a suitable ByteArray
     * is not provided, null is returned.
     * 
     * @param ba ByteArray 16 bytes in length representing a 128-bit UID.
     * 
     * @return String representation of the UID, or null if an invalid
     * ByteArray is provided.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Royale 1.0.0
	 *  @productversion Royale 0.0
     */
    public static function fromBinary(ba:BinaryData):String
    {
        if (ba != null && ba.length >= 16 && ba.bytesAvailable >= 16)
        {
            UIDBuffer.position = 0;
            var index:uint = 0;
            for (var i:uint = 0; i < 16; i++)
            {
                if (i == 4 || i == 6 || i == 8 || i == 10)
                    UIDBuffer.writeByte(DASH); // Hyphen char code

                var b:int = ba.readByte();
                UIDBuffer.writeByte(ALPHA_CHAR_CODES[(b & 0xF0) >>> 4]);
                UIDBuffer.writeByte(ALPHA_CHAR_CODES[(b & 0x0F)]);
            }
            UIDBuffer.position = 0;
            return UIDBuffer.readUTFBytes(UIDBuffer.length);//UIDBuffer.toString(); ??
        }

        return null;
    }

    /**
     * A utility method to check whether a String value represents a 
     * correctly formatted UID value. UID values are expected to be 
     * in the format generated by createUID(), implying that only
     * capitalized A-F characters in addition to 0-9 digits are
     * supported.
     * 
     * @param uid The value to test whether it is formatted as a UID.
     * 
     * @return Returns true if the value is formatted as a UID.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Royale 1.0.0
	 *  @productversion Royale 0.0
     */
    public static function isUID(uid:String):Boolean
    {
        if (uid != null && uid.length == 36)
        {
            for (var i:uint = 0; i < 36; i++)
            {
                var c:Number = uid.charCodeAt(i);

                // Check for correctly placed hyphens
                if (i == 8 || i == 13 || i == 18 || i == 23)
                {
                    if (c != DASH)
                    {
                        return false;
                    }
                }
                // We allow capital alpha-numeric hex digits only
                else if (c < 48 || c > 70 || (c > 57 && c < 65))
                {
                    return false;
                }
            }

            return true;
        }

        return false;
    }

    /**
     * Converts a UID formatted String to a BinaryData. The UID must be in the
     * format generated by createUID, otherwise null is returned.
     * 
     * @param String representing a 128-bit UID
     * 
     * @return ByteArray 16 bytes in length representing the 128-bits of the
     * UID or null if the uid could not be converted.
     *  
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.1
     *  @productversion Royale 1.0.0
	 *  @productversion Royale 0.0
     */
    public static function toBinary(uid:String):BinaryData
    {
        if (isUID(uid))
        {
            var result:BinaryData = new BinaryData();

            for (var i:uint = 0; i < uid.length; i++)
            {
                var c:String = uid.charAt(i);
                if (c == "-")
                    continue;
                var h1:uint = getDigit(c);
                i++;
                var h2:uint = getDigit(uid.charAt(i));
                result.writeByte(((h1 << 4) | h2) & 0xFF);
            }
            result.position = 0;
            return result;
        }

        return null;
    }

    /**
     * Returns the decimal representation of a hex digit.
     * @private
     */
    private static function getDigit(hex:String):uint
    {
        return parseInt(hex,16);
    }
}

}
