pdfjsLib.GlobalWorkerOptions.workerSrc = '../../node_modules/pdfjs-dist/build/pdf.worker.js'

var url = new URLSearchParams(window.location.search.replace('?', '')).get('url')

var eventBus = new pdfjsViewer.EventBus()

/* page counter UI */
var pageCounter = {
  init: function () {
    pageCounter.container = document.getElementById('page-counter')
    pageCounter.input = pageCounter.container.getElementsByTagName('input')[0]
    pageCounter.totalEl = pageCounter.container.querySelector('#total')

    pageCounter.container.addEventListener('click', function () {
      pageCounter.input.focus()
      pageCounter.input.select()
    })

    pageCounter.input.addEventListener('change', function (e) {
      var pageIndex = parseInt(this.value) - 1
      if (pageViews[pageIndex] && pageViews[pageIndex].div) {
        pageViews[pageIndex].div.scrollIntoView()
      }
      updateVisiblePages()
      pageCounter.update()
      pageCounter.input.blur()
    })
  },
  update: function () {
    pageCounter.input.value = currentPage + 1
    pageCounter.totalEl.textContent = pageCount
  }
}

pageCounter.init()

/* progress bar UI */

var progressBar = {
  element: document.getElementById('progress-bar'),
  enabled: false,
  progress: 0,
  incrementProgress: function (progress) { // progress: amount by which to increase the progress bar (number 0-1, 1 = 100%)
    progressBar.progress += progress

    if (!progressBar.enabled) {
      return
    }

    if (progressBar.progress >= 1) {
      progressBar.enabled = false
      progressBar.element.style.transform = 'translateX(0%)'
      setTimeout(function () {
        progressBar.element.hidden = true
      }, 200)
      return
    }

    progressBar.element.hidden = false

    var width = progressBar.progress * 90
    progressBar.element.style.transform = 'translateX(-' + (100 - width) + '%)'
  },
  init: function () {
    setTimeout(function () {
      if (!pdf) {
        progressBar.enabled = true
        progressBar.incrementProgress(0.05)

        var loadingFakeInterval = setInterval(function () { // we can't reliably determine actual download progress, so instead we make the bar move very slowly until the first page has loaded, then show how many pages have rendered
          if (progressBar.progress < 0.125) {
            progressBar.incrementProgress(0.002)
          } else {
            clearInterval(loadingFakeInterval)
          }
        }, 250)
      }
    }, 3000)
  }
}

progressBar.init()

var downloadButton = document.getElementById('download-button')

downloadButton.addEventListener('click', function () {
  downloadPDF()
})

document.querySelectorAll('.side-gutter').forEach(function (el) {
  el.addEventListener('mouseenter', function () {
    showViewerUI()
  })
  el.addEventListener('mouseleave', function () {
    hideViewerUI()
  })
})

function showViewerUI () {
  document.querySelectorAll('.viewer-ui').forEach(el => el.classList.remove('hidden'))
  pageCounter.update()
}

const hideViewerUI = debounce(function () {
  if (!document.querySelector('.side-gutter:hover')) {
    document.querySelectorAll('.viewer-ui').forEach(el => el.classList.add('hidden'))
  }
}, 600)

function updateGutterWidths () {
  var gutterWidth
  if (!pageViews[0]) { // PDF hasn't loaded yet
    gutterWidth = 64
  } else {
    gutterWidth = Math.round(Math.max(64, (window.innerWidth - pageViews[0].viewport.width) / 2)) - 2
  }

  document.querySelectorAll('.side-gutter').forEach(function (el) {
    el.style.width = gutterWidth + 'px'
  })
}

function createContainer () {
  var el = document.createElement('div')
  el.classList.add('page-container')
  document.body.appendChild(el)
  return el
}

var pageBuffer = 15

/* adapted from PDFPageView.draw(), without actually painting the page onto the canvas */
function setupPageDom (pageView) {
  var pdfPage = pageView.pdfPage
  var div = pageView.div
  var canvasWrapper = document.createElement('div')
  canvasWrapper.style.width = div.style.width
  canvasWrapper.style.height = div.style.height
  canvasWrapper.classList.add('canvasWrapper')
  if (pageView.annotationLayer && pageView.annotationLayer.div && !pageView.annotationLayer.div.parentNode) {
    div.appendChild(pageView.annotationLayer.div)
  }
  if (pageView.annotationLayer && pageView.annotationLayer.div) {
    div.insertBefore(canvasWrapper, pageView.annotationLayer.div)
  } else {
    div.appendChild(canvasWrapper)
  }
  var textLayer = null
  if (pageView.textLayerFactory) {
    var textLayerDiv = document.createElement('div')
    textLayerDiv.className = 'textLayer'
    textLayerDiv.style.width = canvasWrapper.style.width
    textLayerDiv.style.height = canvasWrapper.style.height
    if (pageView.annotationLayer && pageView.annotationLayer.div) {
      div.insertBefore(textLayerDiv, pageView.annotationLayer.div)
    } else {
      div.appendChild(textLayerDiv)
    }
    textLayer = pageView.textLayerFactory.createTextLayerBuilder(textLayerDiv, pageView.id - 1, pageView.viewport, pageView.enhanceTextSelection)
  }
  if (pageView.annotationLayerFactory) {
    var annotationLayer = pageView.annotationLayerFactory.createAnnotationLayerBuilder(div, pdfPage, null, null, false, pageView.l10n, null, null, null, null, null)
  }
  pageView.textLayer = textLayer
  pageView.annotationLayer = annotationLayer
  setUpPageAnnotationLayer(pageView)
}

function DefaultTextLayerFactory () { }
DefaultTextLayerFactory.prototype = {
  createTextLayerBuilder: function (textLayerDiv, pageIndex, viewport,
    enhanceTextSelection) {
    return new pdfjsViewer.TextLayerBuilder({
      textLayerDiv: textLayerDiv,
      pageIndex: pageIndex,
      viewport: viewport,
      enhanceTextSelection: true,
      eventBus: eventBus
    })
  }
}

var pageViews = []
var pdf = null

const updateCachedPages = throttle(function () {
  if (currentPage == null) {
    return
  }

  if (!pageViews[currentPage].canvas) {
    redrawPageCanvas(currentPage)
  }

  for (var i = 0; i < pageViews.length; i++) {
    (function (i) {
      if (i === currentPage) {
        // already checked above
        return
      }
      if (Math.abs(i - currentPage) > pageBuffer && pageViews[i].canvas) {
        pageViews[i].canvas.remove()
        pageViews[i].canvas = null
      }
      if (Math.abs(i - currentPage) < pageBuffer && !pageViews[i].canvas) {
        redrawPageCanvas(i)
      }
    })(i)
  }
}, 500)

var pageCount

function setUpPageAnnotationLayer (pageView) {
  pageView.annotationLayer.linkService.goToDestination = async function (dest) {
    // Adapted from https://github.com/mozilla/pdf.js/blob/8ac0ccc2277a7c0c85d6fec41c0f3fc3d1a2d232/web/pdf_link_service.js#L238
    let explicitDest
    if (typeof dest === 'string') {
      explicitDest = await pdf.getDestination(dest)
    } else {
      explicitDest = await dest
    }

    const destRef = explicitDest[0]
    let pageNumber

    if (typeof destRef === 'object' && destRef !== null) {
      pageNumber = await pdf.getPageIndex(destRef)
    } else if (Number.isInteger(destRef)) {
      pageNumber = destRef + 1
    }

    pageViews[pageNumber].div.scrollIntoView()
  }
}

pdfjsLib.getDocument({ url: url, withCredentials: true }).promise.then(async function (pdf) {
  window.pdf = pdf

  pageCount = pdf.numPages

  if (pageCount < 25) {
    pageBuffer = 25
  } else {
    pageBuffer = 4
  }

  pdf.getMetadata().then(function (metadata) {
    document.title = metadata.Title || url.split('/').slice(-1)
  })

  for (var i = 1; i <= pageCount; i++) {
    var pageNumber = i

    await pdf.getPage(pageNumber).then(function (page) {
      progressBar.incrementProgress(1 / pageCount)

      var defaultScale = 1.15
      var minimumPageWidth = 625 // px

      var scale = defaultScale

      var viewport = page.getViewport({ scale: scale })

      if (viewport.width * 1.5 > window.innerWidth) {
        scale = (window.innerWidth / viewport.width) * 0.75

        viewport = page.getViewport({ scale: scale })
      }

      if (viewport.width * 1.33 < minimumPageWidth) {
        scale = (minimumPageWidth / viewport.width) * scale * 0.75
        viewport = page.getViewport({ scale: scale })
      }

      if (pageCount > 200) {
        scale = Math.min(scale, 1.1)
        viewport = page.getViewport({ scale: scale })
      }

      var pageContainer = createContainer()
      var pdfPageView = new pdfjsViewer.PDFPageView({
        container: pageContainer,
        id: pageNumber,
        scale: scale,
        defaultViewport: viewport,
        eventBus: eventBus,
        textLayerFactory: new DefaultTextLayerFactory(),
        annotationLayerFactory: new pdfjsViewer.DefaultAnnotationLayerFactory()
      })
      pdfPageView.setPdfPage(page)
      pageViews.push(pdfPageView)

      if (pageNumber === 1) {
        updateGutterWidths()
      }

      (function (pageNumber, pdfPageView) {
        setTimeout(function () {
          if (pageNumber < pageBuffer || (currentPage && Math.abs(currentPage - pageNumber) < pageBuffer)) {
            pageContainer.classList.add('loading')
            pdfPageView.draw().then(function () { setUpPageAnnotationLayer(pdfPageView) }).then(function () {
              pageContainer.classList.remove('loading')
              if (pageNumber === 1) {
                showViewerUI()
                setTimeout(function () {
                  hideViewerUI()
                }, 4000)
              }
            })
            setTimeout(function () {
              pageContainer.classList.remove('loading')
            }, 2000)
          } else {
            setupPageDom(pdfPageView)
            requestIdleCallback(function () {
              pdfPageView.pdfPage.getTextContent({ normalizeWhitespace: true }).then(function (text) {
                pdfPageView.textLayer.setTextContent(text)
                pdfPageView.textLayer.render(0)
                pdfPageView.annotationLayer.render(pdfPageView.viewport, 'display')
              })
            }, { timeout: 10000 })
          }
        }, 100 * (pageNumber - 1))
      })(pageNumber, pdfPageView)
    })
  }
}).catch(function (e) {
  console.warn('error while loading PDF', e)
  // we can't display a preview, offer to download instead
  downloadPDF()
})

var isFindInPage = false

var currentPage = null

function updateVisiblePages () {
  if (isPrinting) {
    return
  }

  var pageRects = new Array(pageViews.length)

  for (var i = 0; i < pageViews.length; i++) {
    pageRects[i] = pageViews[i].div.getBoundingClientRect()
  }

  var ih = window.innerHeight + 80
  var innerHeight = window.innerHeight

  var visiblePages = []

  for (var i = 0; i < pageViews.length; i++) {
    var rect = pageRects[i]
    var textLayer = pageViews[i].textLayer

    if (!isFindInPage && (rect.bottom < -80 || rect.top > ih)) {
      pageViews[i].div.style.visibility = 'hidden'
      if (textLayer) {
        textLayer.textLayerDiv.style.display = 'none'
      }
    } else {
      pageViews[i].div.style.visibility = 'visible'
      if (textLayer) {
        textLayer.textLayerDiv.style.display = 'block'
      }

      if ((rect.top >= 0 && (innerHeight - rect.top) > innerHeight / 2) || (rect.bottom <= innerHeight && rect.bottom > innerHeight / 2) || (rect.top <= 0 && rect.bottom >= innerHeight)) {
        currentPage = i
      }
    }
  }

  if (currentPage !== undefined) {
    updateCachedPages(currentPage)
  }
}

window.addEventListener('scroll', throttle(function () {
  pageCounter.update()
  updateVisiblePages()
}, 50))

/* keep the UI size constant, regardless of the zoom level.
It would probably be better to add API's in Min for this. */

window.addEventListener('resize', function () {
  // this works in Chromium and Safari, but not in Firefox, and it will probably break at some point.
  window.zoomLevel = window.outerWidth / window.innerWidth

  // make UI elements stay a constant size regardless of zoom level
  document.querySelectorAll('.viewer-ui').forEach(function (el) {
    el.style.zoom = 1 / zoomLevel
  })

  updateGutterWidths()
})

function redrawPageCanvas (i, cb) {
  var canvasWrapperNode = pageViews[i].div.getElementsByClassName('canvasWrapper')[0]
  if (!canvasWrapperNode) {
    return
  }
  var oldCanvas = pageViews[i].canvas
  pageViews[i].paintOnCanvas(canvasWrapperNode).promise.then(function () {
    if (oldCanvas) {
      oldCanvas.remove()
    }
    if (cb) { cb() }
  })
}

var isRedrawing = false

function redrawAllPages () {
  if (isRedrawing) {
    console.log('ignoring redraw')
    return
  }

  isRedrawing = true

  var completedPages = 0
  function pageCompleteCallback () {
    completedPages++
    if (completedPages === Math.min(pageCount, pageBuffer)) {
      isRedrawing = false
    }
  }

  var visiblePageList = []
  var invisiblePageList = []

  // redraw the currently visible pages first

  for (var i = 0; i < pageViews.length; i++) {
    if (!pageViews[i].canvas) {
      continue
    }
    var rect = pageViews[i].div.getBoundingClientRect()
    // if the page is visible, add it to the beginning of the redraw list
    if (rect.top < window.innerHeight && rect.bottom > 0) {
      visiblePageList.push(pageViews[i])
    } else {
      invisiblePageList.push(pageViews[i])
    }
  }

  var redrawList = visiblePageList.concat(invisiblePageList)

  for (var i = 0; i < redrawList.length; i++) {
    (function (i) {
      requestIdleCallback(function () {
        redrawPageCanvas(redrawList[i].id - 1, pageCompleteCallback)
      })
    })(i)
  }
}

var lastPixelRatio = window.devicePixelRatio
window.addEventListener('resize', debounce(function () {
  // update visible pages in case the page size was increased
  updateVisiblePages()

  if (window.devicePixelRatio !== lastPixelRatio) { // if the page was zoomed
    lastPixelRatio = window.devicePixelRatio

    redrawAllPages()
    console.log('redraw triggered')
  }
}, 750))

// https://remysharp.com/2010/07/21/throttling-function-calls

function debounce (fn, delay) {
  var timer = null
  return function () {
    var context = this
    var args = arguments
    clearTimeout(timer)
    timer = setTimeout(function () {
      fn.apply(context, args)
    }, delay)
  }
}

function throttle (fn, threshhold, scope) {
  threshhold || (threshhold = 250)
  var last,
    deferTimer
  return function () {
    var context = scope || this

    var now = +new Date()
    var args = arguments
    if (last && now < last + threshhold) {
      // hold on to it
      clearTimeout(deferTimer)
      deferTimer = setTimeout(function () {
        last = now
        fn.apply(context, args)
      }, threshhold)
    } else {
      last = now
      fn.apply(context, args)
    }
  }
}

function downloadPDF () {
  window.postMessage({ message: 'downloadFile', url })
}

/* printing */

var isPrinting = false

var printPreviousScaleList = []

function afterPrintComplete () {
  for (var i = 0; i < pageViews.length; i++) {
    pageViews[i].viewport = pageViews[i].viewport.clone({ scale: printPreviousScaleList[i] * (4 / 3) })
    pageViews[i].cssTransform({target: pageViews[i].canvas})
  }
  printPreviousScaleList = []
  isPrinting = false
  updateVisiblePages()
}

function printPDF () {
  var begunCount = 0
  var doneCount = 0

  isPrinting = true

  function onAllRenderingDone () {
    // we can print the document now
    setTimeout(function () {
      window.print()
    }, 100)
  }

  function onPageRenderComplete () {
    doneCount++
    if (doneCount === begunCount) {
      onAllRenderingDone()
    }
  }

  // we can't print very large documents because of memory usage, so offer to download the file instead
  if (pageCount > 100) {
    isPrinting = false
    downloadPDF()
  } else {
    var minimumAcceptableScale = 3.125 / devicePixelRatio
    // redraw each page at a high-enough scale for printing
    for (var i = 0; i < pageViews.length; i++) {
      (function (i) {
        printPreviousScaleList.push(pageViews[i].scale)
        var needsScaleChange = pageViews[i].scale < minimumAcceptableScale

        if (needsScaleChange) {
          pageViews[i].viewport = pageViews[i].viewport.clone({ scale: minimumAcceptableScale * (4 / 3) })
        }

        if (needsScaleChange || !pageViews[i].canvas) {
          begunCount++
          redrawPageCanvas(i, function () {
            if (needsScaleChange) {
              pageViews[i].cssTransform({target: pageViews[i].canvas})
            }
            onPageRenderComplete()
          })
        }
      })(i)
    }
    if (begunCount === 0) {
      // we don't have to redraw any pages
      onAllRenderingDone()
    }
  }
}

var mediaQueryList = window.matchMedia('print')
mediaQueryList.onchange = function (mql) {
  if (!mql.matches) {
    setTimeout(function () {
      afterPrintComplete()
    }, 1000)
  }
}

/* find in page mode - make all pages visible so that Chromium's search can search the whole PDF */

function startFindInPage () {
  isFindInPage = true

  for (var i = 0; i < pageViews.length; i++) {
    pageViews[i].div.style.visibility = 'visible'
    if (pageViews[i].textLayer) {
      pageViews[i].textLayer.textLayerDiv.style.display = 'block'
    }
  }
}

function endFindInPage () {
  isFindInPage = false
  updateVisiblePages()
}

/* these functions are called from the parent process */

var parentProcessActions = {
  downloadPDF: downloadPDF,
  printPDF: printPDF,
  startFindInPage: startFindInPage,
  endFindInPage: endFindInPage
}
