// Constants
var kGB =  1000000000;
var kGiB = 1073741824;
var kTiB = 1099511627776;

// Utility function for (ascending) numeric array sorting
function numOrdA(a, b){ return (a-b); }

// Utility function to format byte values in metric or binary units
function FormatByteNumber(pNumber, pDecimalPlaces, pUseMetric)
{
	var vFormattedStr = '';
    var vUnits = new Array(' B', ' KB', ' MB', ' GB', ' TB', ' PB', ' XB');
    var i;
    
	// Sanity check pDecimalPlaces
	pDecimalPlaces = Math.min(20, pDecimalPlaces);
	
    var	vBase10Power = 0;
    var	vBase2Power = 0;
	var	vDivisor = 1;

    if ((pUseMetric && (pNumber > 1000000000000000000)) ||
(pNumber > 1152921504606846976)) // XB
    {
	    vBase10Power = 18;
	    vBase2Power = 60;
    }
    else
    if ((pUseMetric && (pNumber > 1000000000000000)) ||
(pNumber > 1125899906842624)) // PB
    {
	    vBase10Power = 15;
	    vBase2Power = 50;
    }
    else
    if ((pUseMetric && (pNumber > 1000000000000)) ||
(pNumber > 1099511627776)) // TB
    {
	    vBase10Power = 12;
	    vBase2Power = 40;
    }
    else
    if ((pUseMetric && (pNumber > 1000000000)) ||
(pNumber > 1073741824)) // GB
    {
	    vBase10Power = 9;
	    vBase2Power = 30;
    }
    else
    if ((pUseMetric && (pNumber > 1000000)) ||
(pNumber > 1048576)) // MB
    {
	    vBase10Power = 6;
	    vBase2Power = 20;
    }
    else
    if ((pUseMetric && (pNumber > 1000)) ||
(pNumber > 1024)) // KB
    {
	    vBase10Power = 3;
	    vBase2Power = 10;
    }

    if (pUseMetric)
    {
	    for (i = 0; i < vBase10Power; ++i) vDivisor *= 10;

	    var vEdgeCheck = Number(pNumber / vDivisor).toFixed(0);
	    if (vEdgeCheck == '1000')
    vFormattedStr = Number(1.0).toFixed(pDecimalPlaces) + vUnits[(vBase10Power / 3) + 1];
	    else
    vFormattedStr = Number(pNumber / vDivisor).toFixed(pDecimalPlaces) + vUnits[vBase10Power / 3];
    }
    else
    {
	    for (i = 0; i < vBase2Power; ++i) vDivisor *= 2;

	    var vEdgeCheck = Number(pNumber / vDivisor).toFixed(0);
	    if (vEdgeCheck == '1000')
    vFormattedStr = Number(1.0).toFixed(pDecimalPlaces) + vUnits[(vBase2Power / 10) + 1];
	    else
	vFormattedStr = Number(pNumber / vDivisor).toFixed(pDecimalPlaces) + vUnits[vBase2Power / 10];
    }
    
    // There seems to be a bug in "toFixed" on some browsers. Do a quick check for a first character '.'
    // if this is first then add a zero to the beginning of the string.
    if (vFormattedStr.substr(0, 1) == '.')
vFormattedStr = '0' + vFormattedStr;

	return vFormattedStr;
}   

function UnformatByteNumber(pFormattedStr, pUseMetric)
{
	var vResultNumber = parseFloat(pFormattedStr);
    var vUnits = new Array(' KB', ' MB', ' GB', ' TB', ' PB', ' XB');
    var i, j;
 
    var	vBase10Power = 0;
    var	vBase2Power = 0;
    var vMultiplier = 1;
    
 	for (i = 0; i < vUnits.length; ++i)
 	{
vBase10Power += 3;
vBase2Power += 10;

if (String(pFormattedStr).indexOf(vUnits[i]) > 0)
{
    if (pUseMetric)
	    for (j = 0; j < vBase10Power; ++j) vMultiplier *= 10;
    else
	    for (j = 0; j < vBase2Power; ++j) vMultiplier *= 2;
    
    vResultNumber = vResultNumber * vMultiplier;
    break;
}
    }    

	return vResultNumber;
}   
   
// Simple dictionary key/value element class
function SimpleElement() 
{
    // Public Fields
    this.mKey = '';
    this.mValue = '';
}

// VERY Simple Key/Value dictionary class without element removal, hashing, unique key enforcement, etc.
function SimpleDictionary() 
{
    // Private Fields
    var mArray = new Array();
    var mSize = 0;
    
    // Public Methods
    this.Size = Size;  
    this.Add = Add;  
    this.GetIndex = GetIndex;  
    this.GetKey = GetKey;  
    this.GetIndexedKey = GetIndexedKey;  
    this.GetValue = GetValue;  
    this.GetIndexedValue = GetIndexedValue;  
    this.SetValue = SetValue;  
    
    function Size()
    {
return mSize;
    }
    
    function Add(pKey, pValue)
    {
var vSimpleElement = new SimpleElement();
vSimpleElement.mKey = pKey;
vSimpleElement.mValue = pValue;

mArray[mSize++] = vSimpleElement;
    }

    function GetIndex(pKey)
    {
var vResult = 0;

for (i in mArray)
    if (mArray[i].mKey == pKey) 
vResult = i;

return vResult;
    }

    function GetKey(pValue)
    {
var vResult = '';

for (i in mArray)
    if (mArray[i].mValue == pValue) 
vResult = mArray[i].mKey;

return vResult;
    }

    function GetIndexedKey(pIndex)
    {
return mArray[pIndex].mKey;
    }

    function GetValue(pKey)
    {
var vResult = 0;

for (i in mArray)
    if (mArray[i].mKey == pKey)
vResult = mArray[i].mValue;

return vResult;
    }

    function GetIndexedValue(pIndex)
    {
return mArray[pIndex].mValue;
    }

    function SetValue(pKey, pValue)
    {
for (i in mArray)
    if (mArray[i].mKey == pKey)
mArray[i].mValue = pValue;
    }
       }
 
// Drobo class to encapsulate Drobo related data and methods
function Drobo() 
{
    // Private Fields
    var mDataBays = new SimpleDictionary();
    var mDriveSizes = new Array();

    // Public Fields
    this.mDroboDOMElement = null; 
    this.mBytesTotal = 0;
    this.mBytesAvailable = 0;
    this.mBytesSetAside = 0;
    this.mBytesUnprotected = 0;
    this.mBytesOverhead = 0;
    
    // Public Methods
    this.AddDataBay = AddDataBay;  
    this.InsertDrive = InsertDrive;  
    this.RemoveDrive = RemoveDrive;  
    this.CalculateByteValues = CalculateByteValues;
    this.UpdateDroboImage = UpdateDroboImage;

    function AddDataBay(pDataBayID)
    {
mDataBays.Add(pDataBayID, null);
    }

    function InsertDrive(pDataBayID, pDiskDriveElement)
    {
// Update associated data bay elements
$(pDataBayID+'Label').innerHTML = pDiskDriveElement.alt; // Show Size
$(pDataBayID).style.cursor='pointer';    // Indicate databay is active
$(pDataBayID+'Eject').style.cursor='pointer';    // Indicate eject button is active

// If there is already a drive in this databay, remove it 
var vDriveID = mDataBays.GetValue(pDataBayID);
var vDriveSize = gDisksAvailable.GetValue(vDriveID) * kGB;
       
if (vDriveSize > 0)
{
    for (i in mDriveSizes)
if (mDriveSizes[i] == vDriveSize)
{
    mDriveSizes[i] = mDriveSizes[0];
    mDriveSizes.shift();
    break;
}
}
   
// Update the data bay dictionary 
mDataBays.SetValue(pDataBayID, pDiskDriveElement.id);
 
// Update the drive size array 
vDriveSize = gDisksAvailable.GetValue(pDiskDriveElement.id) * kGB;
mDriveSizes.push(vDriveSize);
mDriveSizes.sort(numOrdA);

// Update the Drobo Image to reflect the insertion 
UpdateDroboImage();

// Build the drive summary
BuildDriveSummary();
    }

    function RemoveDrive(pDataBayID)
    {
// Update associated data bay elements
$(pDataBayID+'Label').innerHTML = '';    // Show Size
$(pDataBayID).style.cursor='auto';       // Indicate databay is inactive
$(pDataBayID+'Eject').style.cursor='auto';       // Indicate eject button is inactive
   
// Update the drive size array 
var vDriveID = mDataBays.GetValue(pDataBayID);
var vDriveSize = gDisksAvailable.GetValue(vDriveID) * kGB;

for (i in mDriveSizes)
    if (mDriveSizes[i] == vDriveSize)
    {
mDriveSizes[i] = mDriveSizes[0];
mDriveSizes.shift();
break;
    }
    
mDriveSizes.sort(numOrdA);
   
// Update the data bay dictionary 
mDataBays.SetValue(pDataBayID, null);

// Update the Drobo Image to reflect the insertion 
UpdateDroboImage();
 
// Build the drive summary
BuildDriveSummary();
   }

    function BuildDriveSummary()
    {
// Build the drive summary string
var i;
var vTotalCapacity = 0;
var vDriveSummaryString = '';

for (i = 0; i < mDataBays.Size(); i++)
{
   var vDriveID = mDataBays.GetIndexedValue(i);
   var vDriveSize = gDisksAvailable.GetValue(vDriveID);

   if (vDriveSize > 0)
    {
vTotalCapacity += vDriveSize;

       if (vDriveSummaryString != '')
    vDriveSummaryString += ' + ';
    
vDriveSummaryString += vDriveID;
    }
}

if (vDriveSummaryString != '')
{
    if (vTotalCapacity >= 1000)
    {
var vTotalCapacityInTB = new Number(vTotalCapacity);
vDriveSummaryString = 'Drives: ' + vDriveSummaryString + ' = ' + Number(vTotalCapacity / 1000).toFixed(1) + ' TB';
    }
    else
vDriveSummaryString = 'Drives: ' + vDriveSummaryString + ' = ' + vTotalCapacity + ' GB';
}

if (vDriveSummaryString == '')
    vDriveSummaryString = 'Drives: None';      

// We know the total bytes from the drive summary
mBytesTotal = vTotalCapacity * kGB;
 
if (mBytesTotal == 0)
{
    $('DriveSummaryString').innerHTML = vDriveSummaryString;
    $('DriveSummaryStringActualLP').innerHTML = "";
    $('DriveSummaryStringActual').innerHTML = "";
    $('DriveSummaryStringActualRP').innerHTML = "";
}
else
{
    $('DriveSummaryString').innerHTML = vDriveSummaryString + ' ';
    $('DriveSummaryStringActualLP').innerHTML = "(";
    $('DriveSummaryStringActual').innerHTML = FormatByteNumber(mBytesTotal, 1, false) + ' actual*';
    $('DriveSummaryStringActualRP').innerHTML = ")";
}

// Re-calculate the rest of the byte numbers
CalculateByteValues();
    }

    function CalculateByteValues()
    {
if (mBytesTotal > 0)
{
    // Drive Count
    var vDriveCount = mDriveSizes.length;
    
    // Overhead constants
    var vOverhead_Drobo = 333000000;
    var vOverhead_LUN =  1900000000 + (Number(Math.floor(mBytesTotal / (2 * kTiB))).toFixed(0) * 1900000000);
    var vOverhead_Disk =  350000000;
 
    // Calculate capacities
    mBytesAvailable = 0;
    mBytesUnprotected = 0;
    mBytesSetAside = 0;
    mBytesOverhead = 0;
       
    // Calculate estimated available and unprotected
    if (vDriveCount == 1)
    {
// Available
mBytesAvailable = ((mBytesTotal - vOverhead_Drobo) / 2) - ((2 * vOverhead_LUN) / 3);
       
// Unprotected
//mBytesUnprotected = ((mBytesTotal - vOverhead_Drobo) / 2) - (vOverhead_LUN / 3);
mBytesUnprotected = 0;
 
// Overhead
 					    //mBytesOverhead = mBytesTotal - Math.min(mBytesTotal, mBytesAvailable + mBytesUnprotected);
 					    mBytesOverhead = vOverhead_Drobo + vOverhead_LUN;
       
// In a single drive scenario there should be no set aside
mBytesSetAside = mBytesTotal - Math.min(mBytesTotal, mBytesAvailable + mBytesUnprotected + mBytesOverhead);
    }
    else
    {
// Available
for (i = 0; i < (vDriveCount - 1); i++)
    mBytesAvailable += mDriveSizes[i];
mBytesAvailable -= (vOverhead_Drobo / 2) + ((2 * vOverhead_LUN) / 3) + (vOverhead_Disk * (vDriveCount - 1));
      
// Unprotected
vDriveCount = mDriveSizes.length;
var vLargestSize = mDriveSizes[vDriveCount - 1];
var vNextLargestSize = mDriveSizes[vDriveCount - 2];
    
if (vLargestSize != vNextLargestSize)
    mBytesUnprotected = ((vLargestSize - vNextLargestSize) - vOverhead_Drobo - (vOverhead_LUN / 3));
  
// Overhead
 					    mBytesOverhead = vOverhead_Drobo + vOverhead_LUN;

// Calculate estimated set aside
mBytesSetAside = mBytesTotal - Math.min(mBytesTotal, mBytesAvailable + mBytesUnprotected + mBytesOverhead);
    }

			var vGraphWidth = $('CapacityBar').style.width;
			vGraphWidth = vGraphWidth.substr(0, vGraphWidth.length - 2);

 			$('AvailableBar').style.backgroundColor = 'rgb(70,94,138)';
 			$('UnprotectedBar').style.backgroundColor = 'rgb(151,32,87)';
 			$('SetAsideBar').style.backgroundColor = 'rgb(143,143,140)';
 			$('OverheadBar').style.backgroundColor = 'rgb(201,153,0)';

			$('AvailableBar').left = 0;
			$('AvailableBar').style.width = Math.max(1, Number(vGraphWidth * (mBytesAvailable / mBytesTotal)).toFixed(0)) + 'px';
			$('UnprotectedBar').left = 0;
			$('UnprotectedBar').style.width = Math.max(1, Number(vGraphWidth * (mBytesUnprotected / mBytesTotal)).toFixed(0)) + 'px';
			$('SetAsideBar').left = 0;
			$('SetAsideBar').style.width = Math.max(1, Number(vGraphWidth * (mBytesSetAside / mBytesTotal)).toFixed(0)) + 'px';
			$('OverheadBar').left = 0;
			$('OverheadBar').style.width = Math.max(2, Number(vGraphWidth * (mBytesOverhead / mBytesTotal)).toFixed(0)) + 'px';

			$('AvailableLabel').innerHTML = FormatByteNumber(mBytesAvailable, 1, false);
			$('UnprotectedLabel').innerHTML = FormatByteNumber(mBytesUnprotected, 1, false);
			$('SetAsideLabel').innerHTML = FormatByteNumber(mBytesSetAside, 1, false);
			$('OverheadLabel').innerHTML = FormatByteNumber(mBytesOverhead, 1, false);
}
else
{
 			$('AvailableBar').style.backgroundColor = 'Gray';
 			$('UnprotectedBar').style.backgroundColor = 'Gray';
 			$('SetAsideBar').style.backgroundColor = 'Gray';
 			$('OverheadBar').style.backgroundColor = 'Gray';

    mBytesAvailable = 0;
    mBytesUnprotected = 0;
    mBytesSetAside = 0;
    mBytesOverhead = 0;

			$('AvailableLabel').innerHTML = '';
			$('UnprotectedLabel').innerHTML = '';
			$('SetAsideLabel').innerHTML = '';
			$('OverheadLabel').innerHTML = '';
}
   }

    function UpdateDroboImage()
    {
// Update the Drobo image to reflect the insertion       
var vDataBaysFilled = 0;

for (i = 0; i < mDataBays.Size(); i++)
    if (mDataBays.GetIndexedValue(i) != null)
vDataBaysFilled = vDataBaysFilled | (1 << i);

switch (vDataBaysFilled)
{
    case 0x0: 
$('Drobo').style.backgroundImage = "url('images/drobolator/Drobo-Front-0000.gif')";
    break;
    case 0x1: 
$('Drobo').style.backgroundImage = "url('images/drobolator/Drobo-Front-0001.gif')";
    break;
    case 0x2: 
$('Drobo').style.backgroundImage = "url('images/drobolator/Drobo-Front-0010.gif')";
    break
    case 0x3: 
$('Drobo').style.backgroundImage = "url('images/drobolator/Drobo-Front-0011.gif')";
    break;
    case 0x4: 
$('Drobo').style.backgroundImage = "url('images/drobolator/Drobo-Front-0100.gif')";
    break;
    case 0x5: 
$('Drobo').style.backgroundImage = "url('images/drobolator/Drobo-Front-0101.gif')";
    break;
    case 0x6: 
$('Drobo').style.backgroundImage = "url('images/drobolator/Drobo-Front-0110.gif')";
    break;
    case 0x7: 
$('Drobo').style.backgroundImage = "url('images/drobolator/Drobo-Front-0111.gif')";
    break;
    case 0x8: 
$('Drobo').style.backgroundImage = "url('images/drobolator/Drobo-Front-1000.gif')";
    break
    case 0x9: 
$('Drobo').style.backgroundImage = "url('images/drobolator/Drobo-Front-1001.gif')";
    break;
    case 0xA: 
$('Drobo').style.backgroundImage = "url('images/drobolator/Drobo-Front-1010.gif')";
    break;
    case 0xB: 
$('Drobo').style.backgroundImage = "url('images/drobolator/Drobo-Front-1011.gif')";
    break;
    case 0xC: 
$('Drobo').style.backgroundImage = "url('images/drobolator/Drobo-Front-1100.gif')";
    break;
    case 0xD: 
$('Drobo').style.backgroundImage = "url('images/drobolator/Drobo-Front-1101.gif')";
    break;
    case 0xE: 
$('Drobo').style.backgroundImage = "url('images/drobolator/Drobo-Front-1110.gif')";
    break;
    case 0xF: 
$('Drobo').style.backgroundImage = "url('images/drobolator/Drobo-Front-1111.gif')";
    break;
}       
   }
}

var gDisksAvailable = new SimpleDictionary();
var gDrobo = new Drobo();

