function View()
{
  this.msnry = null;
  this.overlay = null;
  this.container = null;
  this.grid = null;
  this.menu = null;
  var parent = this;

  this.install = function()
  {
    this.overlay = document.getElementById("overlay");
    this.container = document.getElementById("container");
    this.grid = document.getElementById("grid");
    this.menu = document.getElementById("menu");

    if (SETTINGS.USEMASONRY)
    {
      this.msnry = new Masonry('.grid', 
      {
        itemSelector: '.griditem',
        columnWidth: 350,
        gutter: 20,
        fitWidth: true,
        transitionDuration: 0,
      });
    }
  }

  this.display = function(db)
  {
    if (window.showAdd !== undefined && window.showAdd)
    {
      main.add.setOverlay(false);
    }

    // BUILD
    let dbKeys = Object.keys(db);
    let i = 0;
    let contentHtml = '';
    while (i < dbKeys.length) 
    {
      contentHtml += this.buildEntry(db, dbKeys[i]);
      i++;
    }
    this.grid.innerHTML = contentHtml;

    // LAYOUT
    if (SETTINGS.USEMASONRY)
    {
      this.msnry.reloadItems();
      this.msnry.layout();

      if (SETTINGS.MASONRYCOMPLETE || SETTINGS.MASONRYPROGRESS)
      {
        let imgLoad = imagesLoaded( container );
        if (!SETTINGS.MASONRYPROGRESS)
        {
          // When all images finish: redo mansonry layout
          imgLoad.on( 'always', function() { parent.msnry.layout(); } );
        }
        else
        {
          // As images load one by one: redo masonry layout
          imgLoad.on( 'progress', function() { parent.msnry.layout(); } );
        }
      }
    }
  }

  this.buildEntry = function(db, key)
  {
    let value = db[key];
    let itemClass = "griditem";
    if (SETTINGS.WIDEGRIDITEM)
    {
      if (this.isDefined(value.WIDE) && value.WIDE)
      {
        itemClass += " griditem-wide";
      }
      else if (this.isDefined(value.QOTE))
      {
        if (Array.isArray(value.QOTE) && value.QOTE.length > SETTINGS.AUTOWIDETRIGGER)
        {
          itemClass += " griditem-wide";
        } 
      }
    }

    let onclickImage = ``;
    if (SETTINGS.SHOWIMAG && this.isType(value.TYPE, 'image'))
    {
      itemClass += " griditem-image";
      onclickImage = `onclick="main.view.handleImageClick(event, this, '${value.FILE}');"
        style="cursor: pointer;"`;
    }

    let entry = ``;
    entry += `<div class="${itemClass}" id="${SETTINGS.GRIDITEMIDBASE + value.DIID}">`;

    // ITEM DIV
    if (this.isDefined(value.LINK))
    {
      var idUrl = "url";
      if (this.isDefined(value.SEEN) && value.SEEN === "true")
      {
        idUrl = "urlseen";
      }

      // LINK START
      if (SETTINGS.SHOWLINK && !this.isObject(value.LINK))
      {
        // If this item has only one link then make the whole title the link
        entry += `<a class="griditem-link" href="${String(value.LINK)}" id="${idUrl}">`;
      }
    }

    // UPPER CONTENT START
    if (SETTINGS.SHOWUPPER)
    {
      entry += `<div class="griditem-containerupper" ${onclickImage}>`;

      // TITLE
      if (SETTINGS.SHOWTITLE)
      {
        entry += `<div class="griditem-title">${key.to_properCase()}</div>`;
      }

      // LINK END
      if (SETTINGS.SHOWLINK && this.isDefined(value.LINK))
      {
        if (this.isObject(value.LINK))
        {
          for (let l = 0; l < value.LINK.length; l++)
          {
            entry += `<a class="griditem-link" href="${String(value.LINK[l])}" id="${idUrl}">`;
            entry += `<div class="griditem-linkcontainer"><i class="griditem-linkicon fas fa-link"></i><div class="griditem-linktitle">${this.extractRootDomain(value.LINK[l])}</div></div></a>`;
          }
        }
        else
        {
          entry += `<div class="griditem-linkcontainer"><i class="griditem-linkicon fas fa-link"></i><div class="griditem-linktitle">${this.extractRootDomain(value.LINK)}</div></div></a>`;
        }
      }

      // TYPE
      if (SETTINGS.SHOWTYPE && this.isDefined(value.TYPE))
      {

        entry += `<div class="griditem-typecontainer">`;
        for (let tc = 0; tc < value.TYPE.length; tc++)
        {
          const icon = getTypeIconName(value.TYPE[tc]);
          entry += `<a class="griditem-type" href='#type-${value.TYPE[tc]}'>`;
          entry += `<i class="griditem-typeicon ${icon}"></i>`;
          entry += `</a>`;
        }
        entry += `</div>`; // griditem-typecontainer
      }

      // UPPER CONTENT END
      entry += `</div>`;
    }

    // LOWER CONTENT START
    if (SETTINGS.SHOWLOWER)
    {
      entry += `<div class="griditem-containerlower" ${onclickImage}>`;

      // AUTHOR
      if (SETTINGS.SHOWAUTH && this.isDefined(value.AUTH))
      {
        entry += `<div class="griditem-auth"><i class="fas fa-user textIcon"></i>${value.AUTH}</div>`;
      }

      // TAGS
      if (SETTINGS.SHOWTAGS && this.isDefined(value.TAGS))
      {
        entry += `<div class="griditem-tags"><i class="fas fa-tag textIcon"></i>`;
        for (var i = 0; i < value.TAGS.length; i++)
        {
          entry += `<a class="griditem-taglink" href="#tag-${value.TAGS[i]}">${value.TAGS[i]}</a>`;
          if (i+1 !== value.TAGS.length)
          {
            entry += `, `;
          }
        };
        entry += `</div>`;
      }

      // PROJECT
      if (SETTINGS.SHOWPROJ && this.isDefined(value.PROJ))
      {
        entry += `<div class="griditem-proj"><i class="fas fa-leaf textIcon"></i>`;
        for (var i = 0; i < value.PROJ.length; i++)
        {
          entry += `<a class="griditem-taglink" href="#proj-${value.PROJ[i]}">${value.PROJ[i].to_properCase()}</a>`;
          if (i + 1 != value.PROJ.length)
          {
            entry += `, `;
          }
        }
        entry += `</div>`;
      }

      // TERM
      if (SETTINGS.SHOWTERM && this.isDefined(value.TERM))
      {
        entry += this.doMultilineFormatting(value.TERM, "griditem-term", "fas fa-ribbon textIcon");
      }

      // NOTE
      if (SETTINGS.SHOWNOTE && this.isDefined(value.NOTE))
      {
        entry += this.doMultilineFormatting(value.NOTE, "griditem-note", "fas fa-sticky-note textIcon");
      }

      // QUOTE
      if (SETTINGS.SHOWQOTE && this.isDefined(value.QOTE))
      {
        entry += this.doMultilineFormatting(value.QOTE, "griditem-quote", "fas fa-comment textIcon");
      }

      // PROGRESS
      if (SETTINGS.SHOWPROG && this.isDefined(value.PROG))
      {
        entry += `<div class="griditem-prog"><i class="fas fa-clock textIcon"></i>${value.PROG}</div>`;
      }

      // IMAGE - for non-image-type-entry
      if (SETTINGS.SHOWIMAG 
        && !this.isType(value.TYPE, 'image')
        && this.isDefined(value.FILE)
        && this.isImage(value.FILE))
      {
        entry += `<div class="image">`;
        entry += `<img class="griditem-img" src="content/media/${value.FILE}" onclick="main.lightbox.load('content/media/${value.FILE}')">`;
        entry += `</div>`;
      }

      // FILE
      if (SETTINGS.SHOWFILE && this.isDefined(value.FILE))
      {
        if (this.isObject(value.FILE))
        {
          for (var i = 0; i < value.FILE.length; i++) 
          {
            entry += `<div class="griditem-file"><i class="fas fa-folder-open textIcon"></i><a class="griditem-file-link" href="content/media/${value.FILE[i]}">${value.FILE[i]}</a></div>`;
          }
        }
        else
        {
          // single
          entry += `<div class="griditem-file"><i class="fas fa-folder-open textIcon"></i><a class="griditem-file-link" href="content/media/${value.FILE}">${value.FILE}</a></div>`;
        }
      }

      // LOWER CONTENT END
      entry += `</div>`;
    }

    // IMAGE - for image-type-entry
    if (SETTINGS.SHOWIMAG 
        && this.isType(value.TYPE, 'image')
        && this.isDefined(value.FILE)
        && this.isImage(value.FILE))
    {
      entry += `<div class="image">`;
      if (SETTINGS.SHOWOVERLAY)
      {
        entry += `<div class="image-overlay"></div>`;
      }
      entry += `<img class="griditem-image-img" src="content/media/${value.FILE}">`;
      entry += `</div>`;
    }

    entry += `</div>`;
    return entry;
  }

  this.stats = function(value)
  {
    let menuContent = ``;
    if (window.showAdd !== undefined && window.showAdd)
    {
      // ADD
      menuContent += `<div class="menu-itemgroup">`;
      menuContent += `<a href='#add' class="menu-item">`;
      menuContent += `<b>a</b>dd`;
      menuContent += `</a>`;
      menuContent += `</div>`;
    }

    // TOTAL
    menuContent += `<div class="menu-itemgroup">`;
    menuContent += `<a href='#' class="menu-item">`;
    menuContent += `<div class="menu-itemcount">${value.total}</div>`;
    menuContent += `<i class="menu-itemicon fas fa-asterisk"></i>`;
    menuContent += `</a>`;
    menuContent += `</div>`;

    // DONE
    if (SETTINGS.SHOWDONE)
    {
      menuContent += `<div class="menu-itemgroup">`;
      menuContent += `<a href='#done-true' class="menu-item">`;
      menuContent += `<div class="menu-itemcount">${value.done}</div>`;
      menuContent += `<i class="menu-itemicon fas fa-check"></i>`;
      menuContent += `</a>`;
      menuContent += `<a href='#done-false' class="menu-item">`;
      menuContent += `<div class="menu-itemcount">${value.total - value.done}</div>`;
      menuContent += `<i class="menu-itemicon fas fa-times"></i>`;
      menuContent += `</a>`;
      menuContent += `</div>`;
    }

    menuContent += `<div class="menu-itemgroup">`;
    for (let ty = 0; ty < Math.min(value.types.length, SETTINGS.STATSNUMTYPE); ty++) 
    {
      const type = value.types[ty][0];
      const count = value.types[ty][1];
      const icon = getTypeIconName(type);
      menuContent += `<a href='#type-${type}' class="menu-item">`;
      menuContent += `<div class="menu-itemcount">${count}</div>`;
      menuContent += `<i class="menu-itemicon ${icon}"></i>`;
      menuContent += `</a>`;
    }
    menuContent += `</div>`;

    // TERM
    menuContent += `<div class="menu-itemgroup">`;
    if (value.terms > 0)
    {
      // menuContent += `<div class="menu-item-space"></div>`;
      menuContent += `<a href='#term' class="menu-item">`;
      menuContent += `<div class="menu-itemcount">${value.terms}</div>`;
      menuContent += `<i class="menu-itemicon fas fa-ribbon"></i>`;
      menuContent += `</a>`;
    }
    menuContent += `</div>`;

    // TAGS
    menuContent += `<div class="menu-itemgroup">`;
    if (value.tags.length > 0)
    {
      menuContent += `<div class="menu-tagcontainer">`;
      menuContent += `<i class="menu-tagicon fas fa-tag"></i>`;
      for (var t = 0; t < Math.min(value.tags.length, SETTINGS.STATSNUMTAGS); t++) 
      {
        menuContent += `<a class="menu-tag" href='#tag-${value.tags[t][0]}'>`;
        // menuContent += `<i class="fas fa-tag textIcon"></i>`;
        menuContent += `<div class="menu-tagcount">${value.tags[t][1]}</div>`;
        menuContent += `<div class="menu-taglabel">${value.tags[t][0]}</div>`;
        menuContent += `</a>`;
      }
      menuContent += `</div>`;
    }
    menuContent += `</div>`;
    this.menu.innerHTML = menuContent;
  }

  this.handleImageClick = function(e, element, file)
  {
    e = e || window.event;
    var target = e.target || e.srcElement;
    if (target == element)
    {
      // If user is clicking given element, or element's background... 
      // as opposed to an element's child content, then do lightbox.
      // This stops lightbox from happening when clicking on tags, file etc
      main.lightbox.load(`content/media/${file}`);
    }
  }

  this.doMultilineFormatting = function(data, className, iconName)
  {
    let result = '';
    if (Array.isArray(data))
    {
      for (var i in data)
      {
        if (data[i].substring(0, 2) == "> ")
        {
          // New item
          if (data[i].includes(": "))
          {
            let titleSplit = data[i].substring(2).split(': '); // .substring(2) removes the "> "
            for (var e = 0; e < titleSplit.length; e++) 
            {
              titleSplit[e] = titleSplit[e].trim();
            }
            result += `<div class="${className}"><i class="${iconName}"></i><b>${titleSplit[0]}</b>: ${titleSplit[1]}</div>`;
          }
          else
          {
            result += `<div class="${className}"><i class="${iconName}"></i>${data[i].substring(2)}</div>`;
          }
        }
        else if (data[i].substring(0, 2) === "& ")
        {
          // New line in current item
          result += `<div class="${className}">${data[i].substring(2)}</div>`;
        }
        else if (data[i].substring(0, 2) == "- ")
        {
          // Bullet point
          result += `<div class="${className}"><i class="fas fa-caret-right textIcon"></i>${data[i].substring(2)}</div>`;
        }
        else
        {
          // Handle unformatted
          result += `<div class="${className}"><i class="${iconName}"></i>${data[i]}</div>`;
        }
      }
    }
    else
    {
      // Handle not array
      result += `<div class="${className}"><i class="${iconName}"></i>${data}</div>`;
    }
    return result;
  }

  getTypeIconName = function(type)
  {
    let icon = '';
    switch (type) 
    {
        case 'article': icon = 'far fa-newspaper'; break;
        case 'podcast': icon = 'fas fa-podcast'; break;
        case 'video': icon = 'fas fa-tv'; break;
        case 'list': icon = 'fas fa-file-alt'; break;
        case 'book': icon = 'fas fa-book-open'; break;
        case 'game': icon = 'fas fa-gamepad'; break;
        case 'service': icon = 'fas fa-server'; break;
        case 'lecture': icon = 'fas fa-chalkboard-teacher'; break;
        case 'quote': icon = 'fas fa-comment'; break;
        case 'tool': icon = 'fas fa-wrench'; break;
        case 'music': icon = 'fas fa-music'; break;
        case 'image': icon = 'fas fa-image'; break;
        case 'encyclopedia': icon = 'fas fa-globe'; break;
        case 'term': icon = 'fas fa-ribbon'; break;
      }
    return icon;
  }

  // HELPER
  this.isDefined = function(value)
  {
    return (typeof value !== 'undefined');
  }

  this.isObject = function(value)
  {
    return (typeof value == 'object');
  }

  this.isImage = function(filename)
  {
    return (/\.(gif|jpg|jpeg|tiff|png)$/i).test(filename);
  }

  this.isType = function(typeArray, value)
  {
    if (this.isDefined(typeArray))
    {
      for (var i = 0; i < typeArray.length; i++)
      {
        if (typeArray[i] == value)
        {
          return true;
        }
      }
    }
    return false;
  }

  String.prototype.to_properCase = function()
  {
    return this.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();});
  }

  // Source: https://stackoverflow.com/questions/8498592/extract-hostname-name-from-string
  this.extractRootDomain = function(url)
  {
    var domain = this.extractHostname(url),
    splitArr = domain.split('.'),
    arrLen = splitArr.length;

    // extracting the root domain here
    // if there is a subdomain 
    if (arrLen > 2) 
    {
      domain = splitArr[arrLen - 2] + '.' + splitArr[arrLen - 1];
      // check to see if it's using a Country Code Top Level Domain (ccTLD) (i.e. ".me.uk")
      if (splitArr[arrLen - 2].length === 2 && splitArr[arrLen - 1].length === 2)
      {
        // this is using a ccTLD
        domain = splitArr[arrLen - 3] + '.' + domain;
      }
    }
    return domain;
  }

  // Source: https://stackoverflow.com/questions/8498592/extract-hostname-name-from-string
  this.extractHostname = function(url)
  {
    var hostname;
    // find & remove protocol (http, ftp, etc.) and get hostname

    if (url.indexOf("://") > -1)
    {
      hostname = url.split('/')[2];
    }
    else
    {
      hostname = url.split('/')[0];
    }

    // find & remove port number
    hostname = hostname.split(':')[0];
    // find & remove "?"
    hostname = hostname.split('?')[0];

    return hostname;
  }
}