//----------------------------------------------------------------------------
//
//   Module:  ram_formulas.js
//   Version: 2010.0
//   Purpose: RAM computation for file system components
//
//----------------------------------------------------------------------------
//
//   Copyright 2010, Blunk Microsystems
//       ALL RIGHTS RESERVED
//
//----------------------------------------------------------------------------

//----------------------------------------------------------------------------
// globals
//----------------------------------------------------------------------------
var strDevType = "UNKNOWN";
var intFopenMax = 64;
var intFilenameMax = 128;
var intNumBlocks = 0;
var intMaxBadBlocks = 0;
var intBlockSize = 16;
var intPageSize = 512;
var intMaxNumVolumes = 0;
var intVolID = 0;
var intMaxCellIndex = 0;
var intSystemRam = 0;
var intVolumesRam = 0;
var Volumes = new Array();

//----------------------------------------------------------------------------
// setDevType: Figure out device type and based on that enable/disable
//             max bad blocks field
//----------------------------------------------------------------------------
function setDevType()
{
  var orig_type = strDevType;
  
  // figure out which device type is selected
  var dev_type = document.getElementById("device_type");
  if (!dev_type)
    return;   
  strDevType = dev_type.options[dev_type.selectedIndex].value;

  // if NAND or NAND-NDM turn on the max bad blocks field
  if (strDevType == "NAND" || strDevType == "NAND_NDM")
    show("max_bad_blocks_label");
  else
    hide("max_bad_blocks_label");

  // if moving to/from SD/MMC/CF adjust labels for the PageSize, BlockSize and NumberBlocks fields
  if ((strDevType == "SD_MMC_CF" || orig_type == "SD_MMC_CF") && strDevType != orig_type)
  {
    var num_blocks_label = document.getElementById("device_num_blocks_label");

    // if switching from FAT only enable block size, page size and set number of block label back to original
    if (orig_type == "SD_MMC_CF")
    {
      show("block_size_label");
      show("page_size_label");
      num_blocks_label.innerHTML = "Number of Blocks:";
    }
    // else disable block size, page size and rename num blocks to num FAT sectors
    else
    {
      hide("block_size_label");
      hide("page_size_label");
      num_blocks_label.innerHTML = "Number of FAT sectors:";
    }
  }

  // whenever the device type is changed, remove all volumes
  if (strDevType != orig_type)
    removeAllVolumes();

  setBlockSize();
  setPageSize();
}

//----------------------------------------------------------------------------
// setBlockSize: Figure out block size
//----------------------------------------------------------------------------
function setBlockSize()
{
  var block_size = document.getElementById("block_size");
  if (!block_size)
    return;  
  intBlockSize = parseInt(block_size.options[block_size.selectedIndex].value) * 1024;
}
function getBlockSize()
{
  var block_size = document.getElementById("block_size");
  if (!block_size)
    return 0;
  return parseInt(block_size.options[block_size.selectedIndex].value) * 1024;
}

//----------------------------------------------------------------------------
// setPageSize: Figure out page size
//----------------------------------------------------------------------------
function setPageSize()
{
  var page_size = document.getElementById("page_size");
  if (!page_size)
    return;
  intPageSize = parseInt(page_size.options[page_size.selectedIndex].value);
}
function getPageSize()
{
  var page_size = document.getElementById("page_size");
  if (!page_size)
    return 0;
  return parseInt(page_size.options[page_size.selectedIndex].value);
}

//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
function hide(el_id)
{
  var el = document.getElementById(el_id);
  if (el)
    el.style.display = 'none';
}
function show(el_id)
{
  var el = document.getElementById(el_id);
  if (el)
  {
    el.style.display = '';
    el.disabled = false;
  }
}

//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
function getVolObj(obj_lbl, vid)
{
  var i;

  // Look for volume
  for (i = 0;; ++i)
  {
    // if volume not found, return
    if (i == intMaxNumVolumes)
      return {obj:null, vol_indx:-1};
    
    // Skip passed empty volumes
    if (Volumes[i] == null)
      continue;
      
    // if volume found, stop looking
    if (Volumes[i].id == vid)
      break;
  }

  // return handle to object for volume along with volume index in list of volumes
  return {obj:document.getElementById(obj_lbl), vol_indx:i};
}

//----------------------------------------------------------------------------
// setFatType: Set a FAT volume type 12/16/32
//----------------------------------------------------------------------------
function setFatType(vid)
{
  // get handle to volume's FAT type
  var fat_type = getVolObj("VFT_" + vid, vid);
  if (!fat_type.obj)
    return;
  
  // remember selection
  Volumes[fat_type.vol_indx].fat_type = fat_type.obj.value;
}

//----------------------------------------------------------------------------
// setFatClustSz: Set a FAT volume cluster size
//----------------------------------------------------------------------------
function setFatClustSz(vid)
{
  // get handle to volume's FAT cluster size
  var fat_clust_sz = getVolObj("FCS_" + vid, vid);
  if (!fat_clust_sz.obj)
    return;
  
  // remember selection
  Volumes[fat_clust_sz.vol_indx].fat_clust_sz = fat_clust_sz.obj.value;
}

//----------------------------------------------------------------------------
// volHTMLHeader: Create header part of HTML table for a volume display
//----------------------------------------------------------------------------
function volHTMLHeader(vid, vtype)
{
  var selection_type;

  // setup pull down FS options - for SD only FAT available
  if (strDevType == "SD_MMC_CF")
    selection_type = "<span align='right'>Target-FAT</span>";
  
  // for non SD - FFS, FAT+FTL, FTL, ZFS
  else
  {
    var MenuOptions = new Array();
    MenuOptions[0] = "          <option value='NOFS'>Select One</option>";
    MenuOptions[1] = "          <option value='FFS'>TargetFFS</option>";
    MenuOptions[2] = "          <option value='FAT'>TargetFAT/FTL</option>";
    MenuOptions[3] = "          <option value='FTL'>TargetFTL</option>";
    MenuOptions[4] = "          <option value='ZFS'>TargetZFS</option>";

    var i = 0;
    if (vtype == "FFS")
      i = 1;
    else if (vtype == "FAT")
      i = 2;
    else if (vtype == "FTL")
      i = 3;
    else if (vtype == "ZFS")
      i = 4;
    selection_type =
      "        <select class='input_text_select' id='VolType_" + vid + "' onchange='setVolumeType(" + vid + ")'>";
    for (var j = 0; j < 5; ++j)
    {
      if (j == 0 && i)
        continue;
      selection_type += MenuOptions[i];
      i = (i + 1) % 5;
      if (vtype != "NOFS" && i == 0)
      {
        ++i;
        continue;
      }
    }
    selection_type += "        </select>";
  }

  // setup header with FS selection options
  var header =
    "<table><tr><td><table class = 'volume_table'>" +
    "  <tbody>" +
    "    <tr style='position:absolute; top:5px'>"  +
    "      <td colspan='2' class='title_td'><b>Volume #" + vid + " Information</b></td>" +
    "    </tr>" +
    "    <tr><td><br /></td></tr>" +
    "    <tr style='position:absolute; top:40px'>"  +
    "      <td class='input_table_text_td'>" +
    "        Volume Type" +
    "      </td>" +
    "      <td class='input_table_input_td'>" +
    selection_type +
    "      </td>" +
    "    </tr>";

  return header;
}

//----------------------------------------------------------------------------
// volHTMLFooter: Create footer part of HTML table for a volume display
//----------------------------------------------------------------------------
function volHTMLFooter(vid)
{
  var footer =
    "    <tr style='position:absolute; top:254px'>" +
    "      <td colspan='2' align='center'>" +
    "        <a href='javascript:removeVolume(" + intVolID + ")'><b><u>Remove Volume</u></b></href>" +
    "      </td>" +
    "    </tr>" +
    "    <tr style='position:absolute; top:288px'>"  +
    "      <td colspan='2'>" +
    "        <table class='output_table'>" +
    "          <tr>"  +
    "            <td class='output_fixed_td'>Volume RAM:</td>" +
    "            <td class='output_variable_td' id='R_" + intVolID + "'>0 KB</td>" +
    "          </tr>" +
    "        </table>" +
    "      </td>" +
    "    </tr>" +
    "  </tbody>" +
    "</table></td></tr></table>";

  return footer;
}

//----------------------------------------------------------------------------
// volHTMLContent: Create volume contents based on volume/device types  
//----------------------------------------------------------------------------
function volHTMLContent(vid, vtype)
{
  // If no FS volume type selected, no contents
  if (vtype == "NOFS")
    return "";

  var content, num_blocks_label, px_height = 70;

  // All volumes have a 'number of blocks'/'number of FAT sectors' label
  if (strDevType == "SD_MMC_CF")
    number_of_blocks_label = "   Volume Number of FAT sectors:";
  else
    number_of_blocks_label = "        Volume Number of Blocks:";
  content =
    "    <tr style='position:absolute; top:" + px_height + "px'>"  +
    "      <td class='input_table_text_td'>" +
    number_of_blocks_label +
    "      </td>" +
    "      <td class='input_table_input_td'>" +
    "        <input type='text' value='0' class='input_text' id='" + "B_" + vid + "'>" +
    "      </td>" +
    "    </tr>";
  px_height += 30;

  // FFS/FAT volumes have an FOPEN_MAX field
  if (vtype == "FAT" || vtype == "FFS")
  {
    content +=
      "    <tr style='position:absolute; top:" + px_height + "px' id='V05_" + vid + "'>"  +
      "      <td class='input_table_text_td'>" +
      "        Volume FOPEN_MAX:" +
      "      </td>" +
      "      <td class='input_table_input_td'>" +
      "        <input type='text' value='" + intFopenMax + "' class='input_text' id='" + "F_" + vid + "'>" +
      "      </td>" +
      "    </tr>";
    px_height += 30;
  }

  // if not a SD/CF/HD FAT volume, add 2 extra fields based on volume type
  if (strDevType != "SD_MMC_CF")
  {
    // For FAT+FTL/FTL volumes
    if (vtype == "FAT" || vtype == "FTL")
    {
      // If NDM/NOR add FTL cached pages field
      if (strDevType == "NAND_NDM" || strDevType == "NOR")
      {
        content +=
          "    <tr style='position:absolute; top:" + px_height + "px'>"  +
          "      <td class='input_table_text_td' id='VO2_" + vid + "'>" +
          "        FTL Driver 'cached_pages':" +
          "      </td>" +
          "      <td class='input_table_input_td'>" +
          "        <input type='text' value='1' class='input_text' id='" + "FN_" + vid + "'>" +
          "      </td>" +
          "    </tr>";
        px_height += 30;
      }

      // else raw NAND - add FTL map size and partition bytes
      else
      {
        content +=
          "    <tr style='position:absolute; top:" + px_height + "px' id='part_bytes_" + vid + "'>"  +
          "      <td class='input_table_text_td' id='VO1_" + vid + "'>" +
          "        FTL Driver 'part_bytes':" +
          "      </td>" +
          "      <td class='input_table_input_td'>" +
          "        <input type='text' value='64' class='input_text' id='" + "FD_" + vid + "'>" +
          "      </td>" +
          "    </tr>" +
          "    <tr style='position:absolute; top:" + (px_height + 30) + "px'>"  +
          "      <td class='input_table_text_td' id='VO2_" + vid + "'>" +
          "        FTL Driver 'map_size':" +
          "      </td>" +
          "      <td class='input_table_input_td'>" +
          "        <input type='text' value='12288' class='input_text' id='" + "FN_" + vid + "'>" +
          "      </td>" +
          "    </tr>";
        px_height += 60;
      }
    }

    // For FFS/ZFS volumes add avg. number of file/dirs and avg. file/dir name length
    else
    {
      content +=
        "    <tr style='position:absolute; top:" + px_height + "px' id='part_bytes_" + vid + "'>"  +
        "      <td class='input_table_text_td' id='VO1_" + vid + "'>" +
        "        Avg. Number of Files/Dirs:" +
        "      </td>" +
        "      <td class='input_table_input_td'>" +
        "        <input type='text' value='100' class='input_text' id='" + "FD_" + vid + "'>" +
        "      </td>" +
        "    </tr>" +
        "    <tr style='position:absolute; top:" + (px_height + 30) + "px'>"  +
        "      <td class='input_table_text_td' id='VO2_" + vid + "'>" +
        "        Avg. File/Dir Name Length:" +
        "      </td>" +
        "      <td class='input_table_input_td'>" +
        "        <input type='text' value='" + (intFilenameMax / 2) + "' class='input_text' id='" + "FN_" + vid + "'>" +
        "      </td>" +
        "    </tr>";
      px_height += 60;
    }
  }

  // If FAT volume, add FAT cluster size and FAT type selection fields
  if (vtype == "FAT")
  {
    content +=
      "    <tr style='position:absolute; top:" + px_height + "px'>" +
      "      <td class='input_table_text_td'>" +
      "        FAT Cluster Size:" +
      "      </td>" +
      "      <td class='input_table_input_td'>" +
      "        <select class='input_text_select' id='FCS_" + vid + "' onchange='setFatClustSz(" + vid + ")'>" +
      "          <option value='0'>Automatic</option>" +
      "          <option value='512'>  512B</option>" +
      "          <option value='1024'>  1KB</option>" +
      "          <option value='2048'>  2KB</option>" +
      "          <option value='4096'>  4KB</option>" +
      "          <option value='8192'>  8KB</option>" +
      "          <option value='16384'>16KB</option>" +
      "          <option value='32768'>32KB</option>" +
      "        </select>" +
      "      </td>" +
      "    </tr>" +
      "    <tr style='position:absolute; top:" + (px_height + 30) + "px'>" +
      "      <td class='input_table_text_td'>" +
      "        FAT type:" +
      "      </td>" +
      "      <td class='input_table_input_td'>" +
      "        <select class='input_text_select' id='VFT_" + vid + "' + onchange='setFatType(" + vid + ")'>" +
      "          <option value='FATANY'>Automatic</option>" +
      "          <option value='FAT12'>FAT 12</option>" +
      "          <option value='FAT16'>FAT 16</option>" +
      "          <option value='FAT32'>FAT 32</option>" +
      "        </select>" +
      "      </td>" +
      "    </tr>";
    px_height += 60;
  }

  return content;
}

//----------------------------------------------------------------------------
// addNewVolume: Enter information for a new volume
//----------------------------------------------------------------------------
function addNewVolume()
{
  var volumes = document.getElementById("volumes_tr");
  if (!volumes)
    return;
    
  // Create new volume id for this volume
  ++intVolID;
  
  // insert new volume into row of volumes at the end
  var cell = volumes.insertCell(-1);

  var new_vol = new Volume(1, intMaxCellIndex++, intVolID);

  // for SD/MMC/CF devices, volume is already set to TargetFAT - create contents
  var content = "";
  if (strDevType == "SD_MMC_CF")
  {
    content = volHTMLContent(intVolID, "FAT");
    new_vol.type = "FAT";
    intBlockSize = FAT_SECT_SZ;
  }

  // set the HTML for this volume
  cell.innerHTML =
    "<div style='position:relative' id='V_" + intVolID + "'>" +
    volHTMLHeader(intVolID, "NOFS") +
    content +
    volHTMLFooter(intVolID) +
    "</div>";

  // insert volume in volumes array into an empty spot if any, or extend array
  for (var i = 0;; ++i)
  {
    if (i == intMaxNumVolumes)
    {
      Volumes[intMaxNumVolumes] = new_vol;
      ++intMaxNumVolumes;
      break;
    }
    if (Volumes[i] == null)
    {
      Volumes[i] = new_vol;
      break;
    }
  }
}

//----------------------------------------------------------------------------
// constructor for a volume object
//----------------------------------------------------------------------------
function Volume(valid, index, id)
{
  this.type = "NOFS";
  this.index = index;
  this.id = id;
  this.num_blocks = 0;
  this.fopen_max = 0;
  this.avg_files_dirs = 0;
  this.avg_name_length = 0;
  this.part_bytes = 0;
  this.map_size = 0;
  this.fat_type = "FATANY";
  this.fat_clust_sz = 0;
  this.ram = 0;
}

//----------------------------------------------------------------------------
// removeAllVolumes: Remove all valid volumes
//----------------------------------------------------------------------------
function removeAllVolumes()
{
  for (var i = 0; i < intMaxNumVolumes; ++i)
  {
    if (Volumes[i] != null)
      removeVolume(Volumes[i].id);
  }
}

//----------------------------------------------------------------------------
// removeVolume: Remove a volume from list of valid volumes
//----------------------------------------------------------------------------
function removeVolume(vid)
{
  // get handle to the volume's table row
  var obj_indx = getVolObj("volumes_tr", vid);
  if (!obj_indx.obj)
    return;
  var volumes = obj_indx.obj;
  var i = obj_indx.vol_indx;
  
  // remove cell corresponding to volume from table row and mark volume invalid
  var del_index = Volumes[i].index;
  volumes.deleteCell(del_index);
  delete Volumes[i];
  Volumes[i] = null;
  
  // adjust all valid volumes index past this one
  for (i = 0; i < intMaxNumVolumes; ++i)
    if (Volumes[i] != null && Volumes[i].index > del_index)
      --Volumes[i].index;
  --intMaxCellIndex;
  if (intMaxCellIndex == 0)
    intVolID = 0;
}

//----------------------------------------------------------------------------
// setVolumeType: Figure out volume type
//----------------------------------------------------------------------------
function setVolumeType(vid)
{
  // get handle to the volume type
  var volume_type = getVolObj("VolType_" + vid, vid);
  if (!volume_type.obj)
    return;
  
  // remember selection
  Volumes[volume_type.vol_indx].type = volume_type.obj.value;

  // get handle on the HTML table contents for the volume so it can be filled with appropriate fields
  var tbl_html = document.getElementById("V_" + vid);
  if (!tbl_html)
    return;

  // recreate the contents of the volume HTML table based on volume/device type
  tbl_html.innerHTML =
    volHTMLHeader(vid, volume_type.obj.value) +
    volHTMLContent(vid, volume_type.obj.value) +
    volHTMLFooter(vid);
}

//----------------------------------------------------------------------------
// invalidInputs: Check global inputs are valid
// Returns 0 on success, -1 on failure
//----------------------------------------------------------------------------
function invalidInputs()
{
  // check number of blocks is a positive integer
  intNumBlocks = parseInt(document.getElementById("device_num_blocks").value);
  if (isNaN(intNumBlocks) || intNumBlocks < 1)
  {
    if (strDevType == "SD_MMC_CF")
      alert("'Number of sectors' must be a positive integer!");
    else
      alert("'Number of blocks' must be a positive integer!");
    intNumBlocks = 0;
    return -1;
  }
  
  // if NAND device, check number of bad blocks, else set to 0
  if (strDevType == "NAND" || strDevType == "NAND_NDM")
  {
    intMaxBadBlocks = parseInt(document.getElementById("max_bad_blocks").value);
    if (isNaN(intMaxBadBlocks) || intMaxBadBlocks < 0 || intMaxBadBlocks > intNumBlocks - 2)
    {
      alert("'Max. Bad Blocks' must be an integer between 0 and 'Number of Blocks' - 2!");
      intMaxNumBlocks = 0;
      return -1;
    }
  }
  else
    intMaxBadBlocks = 0;
  
  // check global FILENAME_MAX and FOPEN_MAX are positive integers
  intFilenameMax = parseInt(document.getElementById("filename_max").value);
  if (isNaN(intFilenameMax) || intFilenameMax < 1)
  {
    alert("'FILENAME_MAX' must be a positive integer!");
    intFilenameMax = 128;
    return -1;
  }
  intFopenMax = parseInt(document.getElementById("fopen_max").value);
  if (isNaN(intFopenMax) || intFopenMax < 1)
  {
    alert("System 'FOPEN_MAX' must be a positive integer!");
    intFopenMax = 64;
    return -1;
  }
  return 0;
}

//----------------------------------------------------------------------------
// invalidVolumeInputs: Check volume inputs are valid
// Returns 0 on success, -1 on failure
//----------------------------------------------------------------------------
function invalidVolumeInputs(index)
{
  var volume = Volumes[index];
  var lbl = (strDevType == "SD_MMC_CF") ? "FAT sectors" : "blocks";
  
  // Check volume number of blocks is valid
  volume.num_blocks = parseInt(document.getElementById("B_" + volume.id).value);
  if (isNaN(volume.num_blocks) || volume.num_blocks < 1)
  {
    alert("'Volume Number of " + lbl + "' for Volume #" + volume.id + " must be a positive integer!");
    volume.num_blocks = 0;
    return -1;
  }
  if (strDevType == "NAND_NDM")
  {
    if (volume.num_blocks > intNumBlocks - intMaxBadBlocks - 2)
    {
      alert("'Volume Number of Blocks' for Volume #" + volume.id + " plus max bad blocks plus the two NDM reserved blocks exceeds number of blocks on device!");
      volume.num_blocks = 0;
      return -1;
    }
  }
  else if (strDevType == "NAND")
  {
    if (volume.num_blocks > intNumBlocks - intMaxBadBlocks)
    {
      alert("'Volume Number of Blocks' for Volume #" + volume.id + " plus max bad blocks exceeds number of blocks on device!");
      volume.num_blocks = 0;
      return -1;
    }
  }
  else if (volume.num_blocks > intNumBlocks)
  {
    alert("'Volume Number of " + lbl + "' for Volume #" + volume.id + " exceeds number of " + lbl + " on device!");
    volume.num_blocks = 0;
    return -1;
  }
  
  // Check volume fopen_max is valid for non FTL only volumes
  if (volume.type != "FTL")
  {
    volume.fopen_max = parseInt(document.getElementById("F_" + volume.id).value);
    if (isNaN(volume.fopen_max) || volume.fopen_max < 0 || volume.fopen_max > intFopenMax)
    {
      alert("'Volume FOPEN_MAX' for Volume #" + volume.id + " must be a positive integer not bigger than global 'FOPEN_MAX'!");
      volume.num_blocks = 0;
      return -1;
    }
    if (volume.fopen_max == 0)
      volume.fopen_max = intFopenMax;
  }
  
  // check FFS/ZFS volume inputs
  if (volume.type == "FFS" || volume.type == "ZFS")
  {
    volume.avg_files_dirs = parseInt(document.getElementById("FD_" + volume.id).value);
    if (isNaN(volume.avg_files_dirs) || volume.avg_files_dirs < 1)
    {
      alert("'Avg. Number of Files/Dirs' for Volume #" + volume.id + " must be a positive integer!");
      volume.avg_files_dirs = 100;
      return -1;
    }
    volume.avg_name_length = parseInt(document.getElementById("FN_" + volume.id).value);
    if (isNaN(volume.avg_name_length) || volume.avg_name_length < 1)
    {
      alert("'Avg. File/Dir NameLength' for Volume #" + volume.id + " must be a positive integer!");
      volume.avg_name_length = intFilenameMax / 2;
      return -1;
    }
  }

  // check FAT/FTL volume inputs
  else if ((volume.type == "FAT" || volume.type == "FTL") && strDevType != "SD_MMC_CF")
  {
    num_pages = intNumBlocks * intBlockSize / intPageSize;

    // for non SD/MMC/CF volumes, check the FTL inputs
    if (strDevType != "SD_MMC_CF")
    {
      // check 'partition bytes' field for RAW NAND volumes
      if (strDevType == "NAND")
      {
        volume.part_bytes = parseInt(document.getElementById("FD_" + volume.id).value);
        if (isNaN(volume.part_bytes) || volume.part_bytes < 0)
        {
          alert("FTL Driver 'part_bytes' for Volume #" + volume.id + " must be a non-negative integer!");
          volume.part_bytes = 0;
          return -1;
        }
      }

      // check 'map_size'/'cached_mpns' field
      volume.map_size = parseInt(document.getElementById("FN_" + volume.id).value);
      if (isNaN(volume.map_size) || volume.map_size < 0)
      {
        alert("FTL Driver 'map_size' for Volume #" + volume.id + " must be a positive integer!");
        volume.map_size = 0;
        return -1;
      }

      // if no 'map_size', set FTL to be fully mapped
      if (volume.map_size == 0)
        volume.map_size = num_pages;
      else if (volume.map_size < num_pages && volume.part_bytes == 0 && strDevType == "NAND")
      {
        alert("FTL Driver 'part_bytes' for Volume #" + volume.id + " cannot be zero when map is cached!");
        volume.part_bytes = volume.map_size = 0;
        return -1;
      }
    }
  }

  return 0;
}

//----------------------------------------------------------------------------
// getCacheSize: Calculate cache size
// Returns cache size in bytes
//----------------------------------------------------------------------------
function getCacheSize(num_sects, sect_size, temp_sects)
{
  var pool_sz = num_sects * 44; // C->pool_size * sizeof(CacheEntry)
  var hash_tbl_sz = num_sects * 4;
  var sectors = sect_size * (num_sects + temp_sects);
  
  return pool_sz + hash_tbl_sz + sectors;
}

//----------------------------------------------------------------------------
// getMapSize: Calculate map size
// Returns map size in bytes
//----------------------------------------------------------------------------
function getMapSize(num_cached_entries, volume_map_size)
{
  // if whole map fits into RAM, it's not a cached map but a simple lookup table
  if (num_cached_entries <= volume_map_size)
  {
    volume_map_size = num_cached_entries + 1;
    if (volume_map_size <= 0xFFFF)
      return volume_map_size * 2;
    else
      return volume_map_size * 4;
  }
  
  // MAP global
  var ram = 13 * 4;
  
  // Depending on number of entries, compute individual entry RAM bytes
  var map_ptr_size = 4;
  var map_entry_size = 14;
  if (volume_map_size <= 0xFFFE)
  {
    map_ptr_size = 2;
    map_entry_size = 10;
  }
  else if (volume_map_size <= 0xFFFFFE)
  {
    map_ptr_size = 3;
    map_entry_size = 12;
  }
  
  // hash table[map size]
  ram += volume_map_size * map_ptr_size;
  
  // entries[map size]
  ram += volume_map_size * map_entry_size;
  
  return ram;
}

//----------------------------------------------------------------------------
// computeNdmRam: Calculate RAM requirements for an NDM device
//----------------------------------------------------------------------------
function computeNdmRam()
{
  var ram = 0;
  
  // NDM global
  ram += 42 * 4; // basic types/pointers
  ram += 2 * 4   // CircLink - 2 pointers

  // init_bad_blocks[] - max_bad_blocks * sizeof(ui32)
  ram += (intMaxBadBlocks + 1) * 4;
  
  // run_bad_blocks[] - max_bad_blocks * sizeof(ui32)
  ram += (intMaxBadBlocks + 1) * 4;
  
  // tmp_pages + tmp_spare
  ram += (intPageSize + (intPageSize / 512) * 16);
  
  return ram;
}

//----------------------------------------------------------------------------
// computeSystemRam: Calculate RAM requirements for system wide resources
//----------------------------------------------------------------------------
function computeSystemRam()
{
  intSystemRam = 0;
  
  // Files[FOPEN_MAX + 1] from fsysinit.c
  intSystemRam += (intFopenMax + 1) * (15 * 4 + (4 + intFilenameMax));
  
  // if NDM enabled, add NDM RAM to system RAM
  if (strDevType == "NAND_NDM")
    intSystemRam += computeNdmRam();
}

//----------------------------------------------------------------------------
// computeFfsRam: Caclulate RAM requirements for an FFS volume
//----------------------------------------------------------------------------
function computeFfsRam(volume)
{
  // FFS global
  var ram = (53 * 4 + 4); // all the pointer/basic type fields
  ram += (4 * 4 + intFilenameMax); // sys
  ram += 7 * 4; // FlashDriver - 7 pointers/ui32s
  ram += 2 * 4; // CircLink - 2 pointers
  ram += 9 * 4; // FlashDriver

  // compute volume number of sectors and sector size
  var num_sects = intBlockSize / intPageSize * volume.num_blocks;
  var sect_size = intPageSize;
  while (num_sects > 0xFFFB)
  {
    num_sects /= 2;
    sect_size *= 2;
  }
      
  // sect_tbl[] - num_sects * sizeof(Sectors) bytes
  ram += num_sects * (2 * 2); // Sectors - 2 ui16
      
  // blocks[] - num_blocks * sizeof(FFSBlock) bytes
  ram += volume.num_blocks * (2 * 4 + 2 + 2 * 1); // FFSBlock - 2 ui32, ui16, ui8, ui8
      
  // erase_set[] - max_set_blocks * sizeof(int)
  var max_set_blocks = parseInt(volume.num_blocks / 10);
  if (max_set_blocks == 0)
    max_set_blocks = 1;
  ram += max_set_blocks * 4;
      
  // SMM
  var smm_block_sz = 0x400; // SMM_BLOCK_SIZE #define from flashfsp.h
  var names_per_smm_block = parseInt((smm_block_sz - 12) / volume.avg_name_length);
  var smm_blocks = parseInt((volume.avg_files_dirs + names_per_smm_block - 1) / names_per_smm_block);
  smm_blocks = parseInt((smm_blocks * 10) / 8); // assume 80% utilization of SMM
  ram += smm_blocks * smm_block_sz;
      
  // files table(s)
  var num_tables = parseInt((2 * volume.avg_files_dirs + 5 * 20 / 3) / 20); // 20 entries per table + last table partially filled
  ram += num_tables * 800; // 800 bytes per table (40 x 20)
      
  // data cache
  ram += getCacheSize(volume.fopen_max, sect_size, 1);
  
  return ram;
}

//----------------------------------------------------------------------------
// computeFtlNandRam: Calculate RAM requirements for an FTL NAND volume
//----------------------------------------------------------------------------
function computeFtlNandRam(volume)
{
  var num_pages = intBlockSize * intNumBlocks / FAT_SECT_SZ;
  var pages_per_block = intBlockSize / FAT_SECT_SZ;
  
  // FtlNand global
  var ram = 53 * 4 + 4; // all the basic types/pointers fields
  ram += intFilenameMax; // volume name
  ram += 2 * 4; // CircLink - 2 pointers
  
  // two temporary pages
  ram += 2 * (intPageSize + 16 * intPageSize / FAT_SECT_SZ);

  // old pages array
  ram += (intMaxBadBlocks + 1) * 4;
  
  // blocks[num_blocks] of sizeof(FtldBlockEntry) each
  ram += volume.num_blocks * (2 * 2 + 4); // 2 ui16 and a ui8*
  
  // figure out the number of fat pages this volume has
  var num_fat_pages = num_pages - pages_per_block * (intMaxBadBlocks + 4);
  num_fat_pages -= parseInt(intNumBlocks * pages_per_block / 20);

  // if mapping does not fit entirely in memory, account for partition bytes
  if (num_fat_pages >= volume.map_size || volume.map_size == 0)
    ram += volume.num_blocks * volume.part_bytes;
  
  // compute map RAM
  ram += getMapSize(num_fat_pages, volume.map_size);

  return ram;
}

//----------------------------------------------------------------------------
// computeFtlNdmRam: Calculate RAM requirements for an FTL NDM volume
//----------------------------------------------------------------------------
function computeFtlNdmRam(volume)
{
  var num_pages = intBlockSize * intNumBlocks / FAT_SECT_SZ;
  var pages_per_block = intBlockSize / FAT_SECT_SZ;
  
  // FtlNdm global
  var ram = 46 * 4; // all the basic types/pointers fields
  ram += intFilenameMax; // volume name
  ram += 2 * 4; // CircLink - 2 pointers
  
  // figure out how many map pages the volume has
  var vol_pages = parseInt((num_pages - 4 * pages_per_block + intPageSize / 4) / (intPageSize / 4 + 1));
  vol_pages -= parseInt(2 * vol_pages / 100);
  var map_pages = parseInt((4 * vol_pages + intPageSize - 1) / intPageSize);

  // two temporary data pages and one spare area
  ram += (2 * intPageSize + 16 * intPageSize / FAT_SECT_SZ);
  
  // blocks[num_blocks]
  ram += volume.num_blocks * 5;

  // map pages[]
  ram += map_pages * 4;

  // account for map pages cache
  var cached_pages = 1;
  if (map_pages < volume.map_size)
    cached_pages = map_pages;
  else if (volume.map_size)
    cached_pages = volume.map_size;
  ram += 9 + cached_pages * 9 + cached_pages * intPageSize;
  
  return ram;
}

//----------------------------------------------------------------------------
// computeFtlNorRam: Calculate RAM requirements for an FTL NOR volume
//----------------------------------------------------------------------------
function computeFtlNorRam(volume)
{
  var num_pages = intBlockSize * intNumBlocks / FAT_SECT_SZ;
  var pages_per_block = intBlockSize / FAT_SECT_SZ;
  
  // FtlNor glogal
  var ram = 49 * 4 + 4; // all the basic types/pointers fields
  ram += intFilenameMax; // volume name
  ram += 2 * 4; // CircLink - 2 pointers

  // figure out how many map pages the volume has
  var sects_per_block = parseInt(intBlockSize / FAT_SECT_SZ);
  var n = parseInt((4 * (intBlockSize - 2 * 17)) / (17 * 2 + 4 * FAT_SECT_SZ));
  var vol_sects = n * volume.num_blocks - 4 * sects_per_block;
  vol_sects -= parseInt((vol_sects + FAT_SECT_SZ / 4) / (FAT_SECT_SZ / 4 + 1));
  vol_sects -= parseInt(2 * vol_sects / 100);
  var map_pages = parseInt((4 * vol_sects + intPageSize - 1) / intPageSize);

  // one temporary data page
  ram += intPageSize;
  
  // blocks[num_blocks]
  ram += volume.num_blocks * 5;

  // map pages[]
  ram += map_pages * 4;

  // account for map pages cache
  var cached_pages = 1;
  if (map_pages < volume.map_size)
    cached_pages = map_pages;
  else if (volume.map_size)
    cached_pages = volume.map_size;
  ram += 9 + cached_pages * 9 + cached_pages * intPageSize;
  
  return ram;
}

//----------------------------------------------------------------------------
// FAT related constants
//----------------------------------------------------------------------------
var FAT_SECT_SZ = 512;
var MAX12_SCTS = 8400;
var FAT12_MAX_NUM_CLUSTS = 4085;
var MAX16_SCTS = 4194144;
var KB_32 = 32 * 1024;
var BPB_RSVD_SEC_CNT = 1;
var BPB_RSVD_SEC_CNT32 = 38;
var BPB_NUM_FATS = 2;
var ROOT_DIR_SECTS = 32;
var Fat16Optimals =
[
  {fat_sects:      32680, sects_per_clust:  2},
  {fat_sects:     262144, sects_per_clust:  4},
  {fat_sects:     524288, sects_per_clust:  8},
  {fat_sects:    1048576, sects_per_clust: 16},
  {fat_sects:    2097152, sects_per_clust: 32},
  {fat_sects: 0xFFFFFFFF, sects_per_clust: 64}
];
var Fat32Optimals =
[
  {fat_sects:     532480, sects_per_clust:  1},
  {fat_sects:   16777216, sects_per_clust:  8},
  {fat_sects:   33554432, sects_per_clust: 16},
  {fat_sects:   67108864, sects_per_clust: 32},
  {fat_sects: 0xFFFFFFFF, sects_per_clust: 64}
];

//----------------------------------------------------------------------------
// getFatSize: Calculate a FAT table size in number of FAT sectors
//----------------------------------------------------------------------------
function getFatSize(type, num_fat_sects, sects_per_clust)
{
  switch (type)
  {
    case "FAT12":
      return parseInt((3 * (num_fat_sects - BPB_RSVD_SEC_CNT - ROOT_DIR_SECTS) / 2 + (FAT_SECT_SZ - 1) * sects_per_clust) / ((BPB_NUM_FATS * 3) / 2 + sects_per_clust * FAT_SECT_SZ));
    case "FAT16":
      return parseInt((2 * (num_fat_sects - BPB_RSVD_SEC_CNT - ROOT_DIR_SECTS) + (FAT_SECT_SZ - 1) * sects_per_clust) / (BPB_NUM_FATS * 2 + sects_per_clust * FAT_SECT_SZ));
    case "FAT32":
      return parseInt((4 * (num_fat_sects - BPB_RSVD_SEC_CNT32) + (FAT_SECT_SZ - 1) * sects_per_clust) / (BPB_NUM_FATS * 4 + sects_per_clust * FAT_SECT_SZ));
    default:
      return 0;
  }
}

//----------------------------------------------------------------------------
// computeFatRam: Calculate RAM requirements for a FAT volume
//----------------------------------------------------------------------------
function computeFatRam(volume)
{
  var num_fat_sects, type, sects_per_clust, fat_size, data_sects, num_clusts;
  
  num_fat_sects = intNumBlocks * intBlockSize / FAT_SECT_SZ;
  
  // If no FAT type selected, figure out FAT type
  type = volume.fat_type;
  if (type == "FATANY")
  {
    type = "FAT32";
    if (num_fat_sects <= 8400)
      type = "FAT12";
    else if (num_fat_sects <= 4194144)
      type = "FAT16";
  }

  // If no FAT cluster size selected, figure it out
  if (volume.fat_clust_sz == 0)
  {
    if (type == "FAT12")
      for (sects_per_clust = 1;; sects_per_clust *= 2)
      {
        // figure out FAT size in number of FAT sectors
        fat_size = getFatSize(type, num_fat_sects, sects_per_clust);
      
        // figure out how many data sects are there
        data_sects = num_fat_sects - (BPB_RSVD_SEC_CNT + BPB_NUM_FATS * fat_size + ROOT_DIR_SECTS);
      
        // check if number of clusters is good
        num_clusts = data_sects / sects_per_clust + 1;
        if (num_clusts < FAT12_MAX_NUM_CLUSTS)
          break;
      }
    else
    {
      var optimals;
    
      // set the optimals array based on type
      if (type == "FAT16")
        optimals = Fat16Optimals;
      else
        optimals = Fat32Optimals;

      // loop through the optimals array
      for (var i = 0;; ++i)
      {
        if (optimals[i].fat_sects >= num_fat_sects)
        {
          sects_per_clust = optimals[i].sects_per_clust;
          break;
        }
      }
    }
  }
  else
    sects_per_clust = volume.fat_clust_sz / FAT_SECT_SZ;
  
  // figure out FAT size in number of FAT sectos
  fat_size = getFatSize(type, num_fat_sects, sects_per_clust);
  
  // figure out number of data sectors and clusters
  var reserved_sects = (type == "FAT32") ? BPB_RSVD_SEC_CNT32 : BPB_RSVD_SEC_CNT;
  var root_dir_sects = (type == "FAT32") ? 0 : 32;
  data_sects = num_fat_sects - (reserved_sects + BPB_NUM_FATS * fat_size + root_dir_sects);
  num_clusts = parseInt(data_sects / sects_per_clust) + 1;
  
  // now compute RAM; start with FAT globals
  var ram = 34 * 4 + 2 * 2 + 8; // all the pointer/basic type fields
  ram += (4 * 4 + intFilenameMax); // sys
  ram += 2 * 4; // CircLink - 2 pointers

  // bitmaps: free_clusts[num_clusts], dirty_clusts[num_clusts], changed_sects[fat_size]
  ram += 2 * (num_clusts / 8 + 1);
  ram += (fat_size + 7) / 8;
  
  // data cache and fat cache
  ram += getCacheSize(volume.fopen_max, FAT_SECT_SZ * sects_per_clust, 2);
  ram += getCacheSize(22, FAT_SECT_SZ, 0);
  
  // root cache for FAT12 and FAT16
  if (type != "FAT32")
    ram += getCacheSize(5, FAT_SECT_SZ, 1);
    
  return ram;
}

//----------------------------------------------------------------------------
// computeZfsRam: Calculate RAM requirements for a ZFS volume
//----------------------------------------------------------------------------
function computeZfsRam(volume)
{
  // ZFS global
  var ram = 10 * 4; // all the basic types/pointers fields
  ram += (4 * 4 + intFilenameMax); // sys
  ram += 2 * 4; // CircLink - 2 pointers
  
  // compressed block (less than uncompressed block which is 10000 bytes)
  ram += 10000;
      
  // files table
  ram += volume.avg_files_dirs * (12 * 4 + volume.avg_name_length);
  
  return ram;
}

//----------------------------------------------------------------------------
// computeVolumeRam: Calculate RAM requirements for a specific volume
//----------------------------------------------------------------------------
function computeVolumeRam(index)
{
  var volume = Volumes[index];
  
  // compute RAM based on file system type
  switch (volume.type)
  {
    case "FFS":
      volume.ram = computeFfsRam(volume);
      break;
    
    case "FAT":
    case "FTL":
      // Start by computing the FTL RAM requirements if there is an FTL
      switch (strDevType)
      {
        case "NAND":
          volume.ram = computeFtlNandRam(volume);
          break;
          
        case "NAND_NDM":
          volume.ram = computeFtlNdmRam(volume);
          break;
          
        case "NOR":
          volume.ram = computeFtlNorRam(volume);
          break;
        
        default:
          volume.ram = 0;
      }
      
      // Now compute FAT volume RAM requirements if FAT + FTL
      if (volume.type == "FAT")
        volume.ram += computeFatRam(volume);

      break;
    
    case "ZFS":
      volume.ram = computeZfsRam(volume);
      break;
  }
}

//----------------------------------------------------------------------------
// outputRAM: Create string representation of RAM value
// input: ram - in bytes
// output: string representation in KB or B depending on how big
//----------------------------------------------------------------------------
function outputRam(ram)
{
  if (ram < 1024)
    return ram + " B";
  
  // round KB to two decimal places
  return Math.round((ram * 1.0 / 1024) * 100) / 100 + " KB";
}

//----------------------------------------------------------------------------
// getRamUsage: RAM Computation function
//----------------------------------------------------------------------------
function getRamUsage()
{
  // check global inputs
  if (invalidInputs())
    return;
  
  // check each valid volume inputs (also, add total number of volume blocks)
  var num_blocks = 0;
  for (i = 0; i < intMaxNumVolumes; ++i)
    if (Volumes[i] != null)
    {
      if (invalidVolumeInputs(i))
        return;
      num_blocks += Volumes[i].num_blocks;
    }
  
  // check volumes blocks are at most device size
  if (strDevType == "NAND_NDM")
  {
    if (num_blocks > intNumBlocks - intMaxBadBlocks - 2)
    {
      alert("Total number of volume blocks plus max bad blocks plus the two NDM reserved blocks exceeds number of device blocks!");
      return;
    }
  }
  else if (strDevType == "NAND")
  {
    if (num_blocks > intNumBlocks - intMaxBadBlocks)
    {
      alert("Total number of volume blocks plus max bad blocks exceeds number of device blocks!");
      return;
    }
  }
  else if (num_blocks > intNumBlocks)
  {
    if (strDevType == "SD_MMC_CF")
      alert("Total number of volume FAT sectors exceeds number of device FAT sectors!");
    else
      alert("Total number of volume blocks exceeds number of device blocks!");
    return;
  }

  // if page size is less than 512, no FTL/FAT volumes should be present, unless SD/MMC/CF device
  if (intPageSize < 512 && strDevType != "SD_MMC_CF")
    for (i = 0; i < intMaxNumVolumes; ++i)
      if (Volumes[i] && Volumes[i].type == "FAT")
      {
        alert("Volume #" + Volumes[i].id + " is FAT - cannot have device page size less than 512B!");
        return;
      }
  
  // compute system RAM requirements
  computeSystemRam();
  
  // compute each valid volume ram requirements
  intVolumesRam = 0;
  for (i = 0; i < intMaxNumVolumes; ++i)
    if (Volumes[i] != null)
    {
      computeVolumeRam(i);
      intVolumesRam += Volumes[i].ram;
    }
  
  // display total RAM requirements - start by filling in individual volume ones
  for (i = 0; i < intMaxNumVolumes; ++i)
    if (Volumes[i] != null)
    {
      var ram_field = document.getElementById("R_" + Volumes[i].id);
      if (ram_field)
        ram_field.innerHTML = outputRam(Volumes[i].ram);
    }
  document.getElementById("VolumesRam").innerHTML = outputRam(intVolumesRam);
  document.getElementById("SystemRam").innerHTML = outputRam(intSystemRam);
  document.getElementById("TotalRam").innerHTML = outputRam(intVolumesRam + intSystemRam);
}

