<script setup>
import {computed, reactive, ref, watch} from "vue";
import * as XLSX from "xlsx";
import {useQuasar} from "quasar";
import scoreApi from "app/src/api/score/score";

const open = defineModel("open")

/**
 * @typedef {{
 *             "id": number,
 *             "studentName": string,
 *             "studentNumber": string,
 *             "examRatio": number,
 *             "usualScore": number,
 *             "examScore": number,
 *         }} Score
 */

/**
 *
 * @type {import('vue').Ref<Score[]>}
 */
const scoreList = defineModel("scoreList")

const courseInfo = defineModel("courseInfo")

const term = defineModel("term")

const func = defineEmits([
  'onClose', 'onOk',
])

const file = ref()

const columnList = [
  'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
  'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
]

const optionalColumnList = [
  '', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
  'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
]

const tableInfo = reactive({
  sheetIndex: 0,
  studentNumberColumn: 'B',
  studentNameColumn: 'C',
  usualScoreColumn: 'D',
  examScoreColumn: 'E',
  examRatioColumn: 'F',
  startRow: 5
})

const quasar = useQuasar()

watch([tableInfo, file], readExcel)

function readExcel() {
  if (file.value === undefined) {
    return
  }

  const fileReader = new FileReader()
  fileReader.readAsArrayBuffer(file.value)

  fileReader.onload = (e) => parseExcel(e.currentTarget.result)

  fileReader.onerror = (e) => {
    console.warn(e)
    quasar.dialog({
      transitionShow: 'fade',
      transitionHide: 'fade',
      title: '啊哈？',
      message: e
    })
  }
}

function indexOfColumn(column) {
  if (columnList.find(c => c === column)) {
    return XLSX.utils.decode_col(column)
  } else {
    return undefined
  }
}

function checkBasicRow(row, index) {
  if (index < tableInfo.startRow - 1) {
    return false
  }

  function checkNonnull(column) {
    return !!row[indexOfColumn(column)]
  }

  return checkNonnull(tableInfo.studentNumberColumn) &&
    checkNonnull(tableInfo.usualScoreColumn) &&
    checkNonnull(tableInfo.examScoreColumn)
}

/**
 *
 * @type {import('vue').Ref<Score[]>}
 */
const parsedScore = ref([])

function getDefaultExamRatio(studentNumber) {
  for (const score of scoreList.value) {
    if (score.studentNumber !== studentNumber) {
      continue
    }

    if (score.examRatio) {
      return score.examRatio
    } else {
      return courseInfo.value.examRatio
    }
  }

  return courseInfo.value.examRatio
}

function parseExcel(data) {
  const workbook = XLSX.read(data)
  const worksheet = workbook.Sheets[workbook.SheetNames[tableInfo.sheetIndex]]
  const rawData = XLSX.utils.sheet_to_json(worksheet, {header: 1})
  const rows = rawData.filter((r, i) => checkBasicRow(r, i))

  parsedScore.value = rows.map(row => {
      const s = {
        studentNumber: row[indexOfColumn(tableInfo.studentNumberColumn)],
        studentName: row[indexOfColumn(tableInfo.studentNameColumn)],
        usualScore: row[indexOfColumn(tableInfo.usualScoreColumn)],
        examScore: row[indexOfColumn(tableInfo.examScoreColumn)],
        examRatio: row[indexOfColumn(tableInfo.examRatioColumn)],
      }

      const numberExamRatio = Number(s.examRatio)
      if (isNaN(numberExamRatio) || numberExamRatio < 0 || 1 < numberExamRatio) {
        s.examRatio = getDefaultExamRatio(s.studentNumber)
      }

      return s
    }
  )

  tablePreview.value = XLSX.utils.sheet_to_html(worksheet)
}

const tablePreview = ref('')

const viewMode = ref('parseResult')

function getFinalScore(usual, exam, examRatio) {
  let weightedUsual = usual * (1 - examRatio)
  let weightedExam = exam * (examRatio)

  return (weightedUsual + weightedExam).toFixed(2)
}

const loading = ref(false)
const progress = ref('0/0')

async function sleep(ms) {
  return await new Promise(r => setTimeout(r, ms));
}

const uploading = ref(false)
const canceled = ref(false)

async function uploadScores() {
  if (uploading.value) {
    return
  }

  loading.value = true
  const parsedScoreList = parsedScore.value
  const progressInfo = {
    total: parsedScoreList.length,
    count: 0,
  }

  loop:
    for (const parsedScoreElement of parsedScoreList) {
      for (const score of scoreList.value) {
        if (canceled.value) {
          break loop
        }

        if (score.studentNumber.toString() === parsedScoreElement.studentNumber?.toString()) {
          score.usualScore = parsedScoreElement.usualScore
          score.examScore = parsedScoreElement.examScore

          const numberExamRatio = Number(parsedScoreElement.examRatio)
          if (!isNaN(numberExamRatio) && 0 <= numberExamRatio && numberExamRatio <= 1) {
            score.examRatio = parsedScoreElement.examRatio
          }

          try {
            await scoreApi.teacher.updateScore(courseInfo.value.id, term.value, score)
            progressInfo.count++
            progress.value = `${progressInfo.count} / ${progressInfo.total}`
            await sleep(200);
          } catch (e) {
            console.warn(e)
            loading.value = false
          }
        }
      }
    }

  loading.value = false
  open.value = false
  func('onClose')
}

</script>

<template>
  <q-dialog persistent no-esc-dismiss no-shake transition-show="fade" transition-hide="fade"
            v-model="open" full-height full-width maximized>
    <q-layout view="hHh lpR fFf">

      <q-header bordered class="bg-primary text-white">
        <q-toolbar>
          <q-toolbar-title>
            <div class="text-h6">从Excel表格中导入</div>
          </q-toolbar-title>
        </q-toolbar>
      </q-header>

      <q-page-container class="bg-white w-full h-full">
        <div class="flex flex-col gap-4 bg-white p-4 pb-16">
          <p class="text-base">
            在这里可以从Excel表格中导入成绩信息<br>
            Excel表格至少需要存在以下信息：『学生学号』、『平时成绩』、『考试成绩』，可选的信息为『考试成绩比例』<br>
            此处所说的『行号』和『列』均指Excel中行和列的编码（即“A列”，“B列”以及“3行”，“4行”等等，可以在Excel中查看行和列编码），这里的行列编码值与Excel中必须完全一致
          </p>
          <div class="flex flex-row gap-4">
            <q-file class="flex-grow" v-model="file" label="选择文件" accept=".xls, .xlsx"/>
            <q-btn class="h-12 w-40 text-base" color="primary" outline @click="readExcel" :disable="file === undefined"
                   label="读取并解析"/>
          </div>

          <div class="flex flex-row max-sm:flex-col gap-8 mt-4">
            <div class="flex flex-row max-sm:flex-col max-sm:content-start content-center gap-4 flex-grow">
              <q-select class="text-base flex-grow max-sm:w-full" outlined :options="columnList"
                        v-model="tableInfo.studentNumberColumn" label="学号列"/>
              <q-select class="text-base flex-grow max-sm:w-full" outlined :options="columnList"
                        v-model="tableInfo.studentNameColumn" label="姓名列（可选，对最终上传的成绩无影响）"/>
              <q-select class="text-base flex-grow max-sm:w-full" outlined :options="columnList"
                        v-model="tableInfo.usualScoreColumn" label="平时成绩列"/>
              <q-select class="text-base flex-grow max-sm:w-full" outlined :options="columnList"
                        v-model="tableInfo.examScoreColumn" label="考试成绩列"/>
              <q-select class="text-base flex-grow max-sm:w-full" outlined :options="optionalColumnList"
                        v-model="tableInfo.examRatioColumn" label="考试成绩比例列（可选，若无则使用课程默认数值）"/>
            </div>

            <div class="bg-gray-200 rounded max-sm:hidden" style="width: 2px"/>

            <q-input class="text-base flex-grow max-sm:w-full" style="max-width: 8rem" outlined type="number"
                     v-model="tableInfo.startRow" label="数据起始行号"/>
          </div>

          <q-separator/>
          <div class="flex flex-row gap-4">
            <q-radio v-model="viewMode" val="parseResult" label="解析结果预览"/>
            <q-radio v-model="viewMode" val="excel" label="原始Excel查看"/>
          </div>
          <q-separator/>

          <transition name="fade" mode="out-in">
            <div v-if="viewMode === 'excel'" class="styled-table" v-html="tablePreview"></div>
            <div v-else class="styled-table">
              <table>
                <tbody>
                <tr>
                  <th>学号</th>
                  <th>姓名</th>
                  <th>平时成绩</th>
                  <th>考试成绩</th>
                  <th>考试成绩比例</th>
                  <th>最终成绩</th>
                </tr>
                <template v-for="score in parsedScore" :key="score">
                  <tr>
                    <th>{{ score.studentNumber }}</th>
                    <th>{{ score.studentName }}</th>
                    <th>{{ score.usualScore }}</th>
                    <th>{{ score.examScore }}</th>
                    <th>{{ score.examRatio }}</th>
                    <th>{{ getFinalScore(score.usualScore, score.examScore, score.examRatio) }}</th>
                  </tr>
                </template>
                </tbody>
              </table>
            </div>
          </transition>
        </div>
      </q-page-container>

      <q-footer bordered class="bg-grey-8 text-white">
        <q-toolbar>
          <q-toolbar-title>
            <div class="flex flex-row">
              <q-btn flat v-close-popup>算了</q-btn>
              <q-btn flat @click="uploadScores">确认上传</q-btn>
            </div>
          </q-toolbar-title>
        </q-toolbar>
      </q-footer>

    </q-layout>

    <q-inner-loading
      :showing="loading"
      :label="`上传中：${progress}`"
      label-class="text-teal"
      label-style="font-size: 1.1em"
      color="gray"
      class="z-10"
    />
  </q-dialog>
</template>

<style scoped lang="sass">
.fade-enter-active,
.fade-leave-active
  transition: opacity 0.2s ease

.fade-enter-from,
.fade-leave-to
  opacity: 0
</style>

<style lang="sass">

.styled-table
  font-size: 1rem

  table
    width: 100%
    border-collapse: collapse

  table caption
    font-size: 2rem
    font-weight: bold
    margin: .5em 0

  th, td
    border: 1px solid #999
    text-align: center
    padding: .5rem 0

  table thead tr
    background-color: #008c8c
    color: #fff

  table tbody tr:nth-child(odd)
    background-color: #eee

  table tbody tr:hover
    background-color: #ccc

  table tbody tr td:first-child
    color: #f40

  table tfoot tr td
    text-align: right
    padding-right: .5rem
</style>
