<!DOCTYPE html>
<title>Storage</title>
<style>
  body {
    font-family: sans-serif;
    max-width: 100ch;
    margin: 0 auto;
    padding: 1em;
    background-color: #333;
    color: #eee;
  }
  td {
    text-align: right;
    padding: .5em;
  }
  td:first-child {
    text-align: left;
  }
  a {
    color: inherit;
    text-decoration: none;
  }
</style>
<div>
  <h2>Quick file upload</h2>
  <p>Uses parallel WebSocket connections for increased bandwidth /api/upload</p>
  <input type=file id=fileInput>
  <progress id=progressBar value=0 max=1></progress>
</div>

<div>
  <h2>Files</h2>
  <ul id=file_list></ul>
</div>

<script>
let files = {}
let flatfiles = {}

function createWatchSocket() {
  const wsurl = new URL("/api/watch", location.href.replace(/^http/, 'ws'))
  const ws = new WebSocket(wsurl)
  ws.onmessage = event => {
    msg = JSON.parse(event.data)
    if (msg.update) {
      tree_update(msg.update)
      file_list(files)
    } else {
      console.log("Unkonwn message from watch socket", msg)
    }
  }
}

createWatchSocket()

function tree_update(msg) {
  console.log("Tree update", msg)
  let node = files
  for (const elem of msg) {
    if (elem.deleted) {
      const p = node.dir[elem.name].path
      delete node.dir[elem.name]
      delete flatfiles[p]
      break
    }
    if (elem.name !== undefined) node = node.dir[elem.name] ||= {}
    if (elem.size !== undefined) node.size = elem.size
    if (elem.mtime !== undefined) node.mtime = elem.mtime
    if (elem.dir !== undefined) node.dir = elem.dir
  }
  // Update paths and flatfiles
  files.path = "/"
  const nodes = [files]
  flatfiles = {}
  while (node = nodes.pop()) {
    flatfiles[node.path] = node
    if (node.dir === undefined) continue
    for (const name of Object.keys(node.dir)) {
      const child = node.dir[name]
      child.path = node.path + name + (child.dir === undefined ? "" : "/")
      nodes.push(child)
    }
  }
}

var collator = new Intl.Collator(undefined, {numeric: true, sensitivity: 'base'});

const compare_path = (a, b) => collator.compare(a.path, b.path)
const compare_time = (a, b) => a.mtime > b.mtime

function file_list(files) {
  const table = document.getElementById("file_list")
  const sorted = Object.values(flatfiles).sort(compare_time)
  table.innerHTML = ""
  for (const f of sorted) {
    const {path, size, mtime} = f
    const tr = document.createElement("tr")
    const name_td = document.createElement("td")
    const size_td = document.createElement("td")
    const mtime_td = document.createElement("td")
    const a = document.createElement("a")
    table.appendChild(tr)
    tr.appendChild(name_td)
    tr.appendChild(size_td)
    tr.appendChild(mtime_td)
    name_td.appendChild(a)
    size_td.textContent = size
    mtime_td.textContent = formatUnixDate(mtime)
    a.textContent = path
    a.href = `/files${path}`
    /*a.onclick = event => {
      if (window.showSaveFilePicker) {
        event.preventDefault()
        download_ws(name, size)
      }
    }
    a.download = ""*/
  }
}

function formatUnixDate(t) {
    const date = new Date(t * 1000)
    const now = new Date()
    const diff = date - now
    const formatter = new Intl.RelativeTimeFormat('en', { numeric: 'auto' })

    if (Math.abs(diff) <= 60000) {
        return formatter.format(Math.round(diff / 1000), 'second')
    }

    if (Math.abs(diff) <= 3600000) {
        return formatter.format(Math.round(diff / 60000), 'minute')
    }

    if (Math.abs(diff) <= 86400000) {
        return formatter.format(Math.round(diff / 3600000), 'hour')
    }

    if (Math.abs(diff) <= 604800000) {
        return formatter.format(Math.round(diff / 86400000), 'day')
    }

    return date.toLocaleDateString()
}

async function download_ws(name, size) {
  const fh = await window.showSaveFilePicker({
    suggestedName: name,
  })
  const writer = await fh.createWritable()
  writer.truncate(size)
  const wsurl = new URL("/api/download", location.href.replace(/^http/, 'ws'))
  const ws = new WebSocket(wsurl)
  let pos = 0
  ws.onopen = () => {
    console.log("Downloading over WebSocket", name, size)
    ws.send(JSON.stringify({name, start: 0, end: size, size}))
  }
  ws.onmessage = event => {
    if (typeof event.data === 'string') {
      const msg = JSON.parse(event.data)
      console.log("Download finished", msg)
      ws.close()
      return
    }
    console.log("Received chunk", name, pos, pos + event.data.size)
    pos += event.data.size
    writer.write(event.data)
  }
  ws.onclose = () => {
    if (pos < size) {
      console.log("Download aborted", name, pos)
      writer.truncate(pos)
    }
    writer.close()
  }
}

const fileInput = document.getElementById("fileInput")
const progress = document.getElementById("progressBar")
const numConnections = 2
const chunkSize = 1<<20
const wsConnections = new Set()

//for (let i = 0; i < numConnections; i++) createUploadWS()

function createUploadWS() {
  const wsurl = new URL("/api/upload", location.href.replace(/^http/, 'ws'))
  const ws = new WebSocket(wsurl)
  ws.binaryType = 'arraybuffer'
  ws.onopen = () => {
    wsConnections.add(ws)
    console.log("Upload socket connected")
  }
  ws.onmessage = event => {
    msg = JSON.parse(event.data)
    if (msg.written) progress.value += +msg.written
    else console.log(`Error: ${msg.error}`)
  }
  ws.onclose = () => {
    wsConnections.delete(ws)
    console.log("Upload socket disconnected, reconnecting...")
    setTimeout(createUploadWS, 1000)
  }
}

async function load(file, start, end) {
  const reader = new FileReader()
  const load = new Promise(resolve => reader.onload = resolve)
  reader.readAsArrayBuffer(file.slice(start, end))
  const event = await load
  return event.target.result
}

async function sendChunk(file, start, end, ws) {
  const chunk = await load(file, start, end)
  ws.send(JSON.stringify({
    name: file.name,
    size: file.size,
    start: start,
    end: end
  }))
  ws.send(chunk)
}

fileInput.addEventListener("change", async function() {
  const file = this.files[0]
  const numChunks = Math.ceil(file.size / chunkSize)
  progress.value = 0
  progress.max = file.size

  console.log(wsConnections)
  for (let i = 0; i < numChunks; i++) {
    const ws = Array.from(wsConnections)[i % wsConnections.size]
    const start = i * chunkSize
    const end = Math.min(file.size, start + chunkSize)
    const res = await sendChunk(file, start, end, ws)
  }
})

</script>