sub Main()
  print "BrightSign / Nowsignage Installer"

  update_endpoint = "https://cdn.nowsignage.com/me-central-1/brightsign/series4"

  videoMode = CreateObject("roVideoMode")
  width = videoMode.GetResX()
  height = videoMode.GetResY()

  screen = CreateObject("roRectangle", 0, 0, width, height)

  CreateAsciiFileFromBase64String("sd:/install.html", GetEncodedInstallationPage())

  config = {
    inspector_server: { port: 2999 },
    brightsign_js_objects_enabled: true,
    url: "file:///sd:/install.html"
  }
  htmlWidget = CreateObject("roHtmlWidget", screen, config)
  htmlWidget.Show()

  Sleep(15000)
  m.port = CreateObject("roMessagePort")
  data = GetLatestVersionInfo(update_endpoint)
  Sleep(250)
  if data <> Invalid
    title = "Installing NowSignage version " + data.version
    htmlWidget.PostJSMessage({ title: title })

    CreateFileFromUrl(update_endpoint + "/autorun.zip", "sd:/update.zip")
    Sleep(5000)
    if HasValidChecksum("sd:/update.zip", data.checksum)
      PerformInstallation()
      htmlWidget.PostJSMessage({
        loading: false,
        description: "Download complete. Rebooting ..."
      })
      Sleep(3000)

      RebootSystem()
    else
      DeleteFile("sd:/update.zip")
      htmlWidget.PostJSMessage({
        loading: false,
        description: "Could not verify file integrity, installation aborted"
      })
    end if
  else
    Sleep(1500)
    htmlWidget.PostJSMessage({
      loading: false,
      description: "Could not contact download server, installation aborted. Please make sure the device is connected to the internet"
    })
  end if

  while true
    msg = Wait(1000, m.port)
  end while
end sub

function GetEncodedInstallationPage()
  return "PGh0bWw+CiAgPGJvZHk+CiAgICA8c3R5bGU+CiAgICAgKiB7IG1hcmdpbjogMDsgcGFkZGluZzogMCB9CiAgICAgYm9keSB7IGJhY2tncm91bmQtY29sb3I6ICMzMTJmMzI7IH0KICAgICBkaXYgewogICAgICAgICB0ZXh0LWFsaWduOiBjZW50ZXI7CiAgICAgICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTsKICAgICAgICAgd2lkdGg6IDEwMCU7CiAgICAgICAgIHRvcDogY2FsYyg1MCUgLSA1ZW0pOwogICAgIH0KICAgICBoMSwgcCB7IGZvbnQtZmFtaWx5OiBhcmlhbDsgZm9udC1zaXplOiA1ZW07IGNvbG9yOiAjZmZmIH0KICAgICBwIHsKICAgICAgICAgZGlzcGxheTogbm9uZTsKICAgICAgICAgbWFyZ2luLXRvcDogMWVtOwogICAgICAgICBmb250LXNpemU6IDJlbTsKICAgICAgICAgY29sb3I6ICMwZjA7CiAgICAgICAgIHRleHQtdHJhbnNmb3JtOiB1cHBlcmNhc2U7CiAgICAgICAgIGFuaW1hdGlvbjogYmxpbmtlciAxLjdzIGN1YmljLWJlemllciguNSwgMCwgMSwgMSkgaW5maW5pdGUgYWx0ZXJuYXRlOwogICAgICAgICBwYWRkaW5nOiAwIDVlbTsKICAgICB9CiAgICAgQGtleWZyYW1lcyBibGlua2VyIHsgdG8geyBvcGFjaXR5OiAwOyB9IH0KICAgIDwvc3R5bGU+CiAgICA8ZGl2PgogICAgICA8aDEgaWQ9InRpdGxlIj5JbnN0YWxsaW5nIE5vd1NpZ25hZ2U8L2gxPgogICAgICA8c3ZnIHdpZHRoPSIxMGVtIiBoZWlnaHQ9IjEwZW0iIHZpZXdCb3g9IjAgMCAxMDAgMTAwIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCIgaWQ9ImxvYWRpbmciIHN0eWxlPSJiYWNrZ3JvdW5kOiByZ2JhKDAsIDAsIDAsIDApIG5vbmUgcmVwZWF0IHNjcm9sbCAwJSAwJTsiPjxjaXJjbGUgY3g9IjUwIiBjeT0iNTAiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzUxQ0FDQyIgc3Ryb2tlLXdpZHRoPSIxMCIgcj0iMzAiIHN0cm9rZS1kYXNoYXJyYXk9IjE0MS4zNzE2Njk0MTE1NDA2NyA0OS4xMjM4ODk4MDM4NDY4OSI+PGFuaW1hdGVUcmFuc2Zvcm0gYXR0cmlidXRlTmFtZT0idHJhbnNmb3JtIiB0eXBlPSJyb3RhdGUiIGNhbGNNb2RlPSJsaW5lYXIiIHZhbHVlcz0iMCA1MCA1MDszNjAgNTAgNTAiIGtleVRpbWVzPSIwOzEiIGR1cj0iMnMiIGJlZ2luPSIwcyIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiPjwvYW5pbWF0ZVRyYW5zZm9ybT48L2NpcmNsZT48L3N2Zz4KICAgICAgPHAgaWQ9ImRlc2NyaXB0aW9uIj48L3A+CiAgICAgIDxzY3JpcHQ+CiAgICAgICBsZXQgdGl0bGVFbGVtZW50ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoInRpdGxlIik7CiAgICAgICBsZXQgZGVzY3JpcHRpb25FbGVtZW50ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoImRlc2NyaXB0aW9uIik7CiAgICAgICBsZXQgbG9hZGluZ0VsZW1lbnQgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgibG9hZGluZyIpOwoKICAgICAgIHZhciBic01lc3NhZ2UgPSBuZXcgQlNNZXNzYWdlUG9ydCgpOwogICAgICAgYnNNZXNzYWdlLm9uYnNtZXNzYWdlID0gZnVuY3Rpb24oeyBkYXRhIH0pIHsKICAgICAgICAgaWYoZGF0YS50aXRsZSAhPT0gdW5kZWZpbmVkKSB7CiAgICAgICAgICAgdGl0bGVFbGVtZW50LmlubmVyVGV4dCA9IGRhdGEudGl0bGU7CiAgICAgICAgIH0KICAgICAgICAgaWYoZGF0YS5kZXNjcmlwdGlvbiAhPT0gdW5kZWZpbmVkKSB7CiAgICAgICAgICAgZGVzY3JpcHRpb25FbGVtZW50LmlubmVyVGV4dCA9IGRhdGEuZGVzY3JpcHRpb247CiAgICAgICAgICAgZGVzY3JpcHRpb25FbGVtZW50LnN0eWxlLmRpc3BsYXkgPSAiYmxvY2siOwogICAgICAgICB9CiAgICAgICAgIGlmKGRhdGEubG9hZGluZyAhPT0gdW5kZWZpbmVkKSB7CiAgICAgICAgICAgbG9hZGluZ0VsZW1lbnQuc3R5bGUuZGlzcGxheSA9IHBhcnNlSW50KGRhdGEubG9hZGluZykgPyAiYmxvY2siIDogIm5vbmUiOwogICAgICAgICB9CiAgICAgICB9CiAgICAgIDwvc2NyaXB0PgogICAgPC9kaXY+CiAgPC9ib2R5Pgo8L2h0bWw+Cg=="
end function

sub CreateAsciiFileFromBase64String(filePath, data)
  byteArray = CreateObject("roByteArray")
  byteArray.FromBase64String(data)
  text = byteArray.ToAsciiString()

  WriteAsciiFile(filePath, text)
end sub

function GetLatestVersionInfo(baseUrl)
  data = GetStringFromUrl(baseUrl + "/version.json", 2000)

  if data = Invalid
    return Invalid
  end if

  return ParseJson(data)
end function

sub CreateFileFromUrl(url, filePath)
  urlTransfer = CreateObject("roURLTransfer")
  urlTransfer.SetURL(url)
  urlTransfer.SetPort(m.port)

  urlTransfer.GetToFile(filePath)
end sub

function GetStringFromUrl(url, timeout)
  urlTransfer = CreateObject("roURLTransfer")
  urlTransfer.SetURL(url)
  urlTransfer.SetPort(m.port)

  if urlTransfer.AsyncGetToString()
    event = Wait(timeout, m.port)

    if type(event) = "roUrlEvent"
      return event.GetString()
    elseif event = Invalid
      urlTransfer.AsyncCancel()
    end if
  end if

  return Invalid
end function

function HasValidChecksum(filePath, expectedChecksum)
  checksum = GetChecksum(filePath)

  return LCase(checksum) = LCase(expectedChecksum)
end function

function GetChecksum(filePath)
  byteArray = CreateObject("roByteArray")
  byteArray.ReadFile(filePath)
  hashGenerator = CreateObject("roHashGenerator", "SHA256")

  return hashGenerator.hash(byteArray).ToHexString()
end function

sub PerformInstallation()
  'Unarchiving removes every file, including the pin,
  'so let's store it in memory and recreate the file later
  pin = GetScreenPin()

  package = CreateObject("roBrightPackage", "sd:/update.zip")
  package.Unpack("sd:/")
  DeleteFile("sd:/update.zip")

  if pin <> Invalid
    WriteAsciiFile("sd:/pin.txt", pin)
  end if
end sub

function GetScreenPin()
  files = MatchFiles("sd:/", "pin.txt")

  if files.Count() > 0
    return ReadAsciiFile("sd:/pin.txt").Trim()
  else
    return Invalid
  end if
end function
