<template>
  <div>
    <h2>序列码批量操作</h2>
    <hr>
    <div>
      <div v-if="cur_step == 1">
        <h4>选择数据</h4>
        序列码范围：
        <select class="form-control w-50" v-model="selection_type">
          <option value="seq_range">按流水号范围</option>
          <option value="code_input">输入序列码</option>
          <option value="code_file">导入序列码文件</option>
          <option value="all">本账户下所有序列码</option>
        </select>
        <div v-if="selection_type == 'seq_range'" class="my-1">
          开始
          <input class="form-control w-50 my-1" type="number" v-model="range_begin" />
          结束 （可选）
          <input class="form-control w-50 my-1" type="number" v-model="range_end" />
        </div>
        <div v-if="selection_type == 'code_input'" class="my-1">
          <textarea class="form-control w-50 my-1" v-model="code_str"
            :maxlength="code_str_maxlength" rows=10></textarea>
          <i class="py-1">
            最大可输入1MB文本
          </i>
        </div>
        <div v-if="selection_type == 'code_file'">
          <input class="form-control w-50 my-1" type="file" @change="set_code_file" />
        </div>
        <div class="my-1">
          <button @click="$router.go(-1)" class="btn btn-info prevstep">返回</button>
          <button class="btn btn-primary nextstep" @click="nextstep">下一步</button>
        </div>
      </div>
      <div v-if="cur_step == 2" class="my-1">
        <h4>选择要执行的操作</h4>
        <select class="form-control w-50 my-2" v-model="batch_op">
          <option value="bind_product">绑定产品</option>
          <option value="unbind_product">取消产品绑定</option>
          <option value="activate">激活</option>
          <option value="deactivate">取消激活</option>
        </select>
        <div v-if="batch_op == 'bind_product'" class="my-1">
          <GenericSelector ref="product_selector" uri="/api/v1/product/" display_field="name" @select="(item) => selected_product_id = item.id" />
        </div>
        <button class="btn btn-secondary prevstep" @click="cur_step--">上一步</button>
        <button class="btn btn-primary nextstep" :disabled="busy" @click="nextstep">下一步</button>
      </div>
      <div v-if="cur_step == 3">
        <h4>操作预览</h4>
        <div v-if="show_affected_rows">
          <div v-if="!busy">
            确定要对<strong>{{ nr_affected_rows }}</strong>个序列码，进行以下操作吗？
          </div>
          <div v-else>
            正在统计将要进行以下操作的序列码数量，请稍等...
          </div>
        </div>
        <div v-else>
          确定要对文件<strong>{{ code_file.name }}</strong>中所有序列码进行以下操作吗？
        </div>
        <div class="alert alert-info my-2">
          <div v-if="batch_op == 'bind_product'">
            绑定产品
          </div>
          <div v-if="batch_op == 'unbind_product'">
            取消产品绑定
          </div>
          <div v-if="batch_op == 'activate'">
            激活
          </div>
          <div v-if="batch_op == 'deactivate'">
            冻结
          </div>
        </div>

        <button class="btn btn-secondary prevstep" @click="cur_step--">上一步</button>
        <button class="btn btn-primary nextstep" @click="submit" :disabled="busy">提交</button>
      </div>
      <div v-if="cur_step == 4">
        <div>
          正在执行操作... ({{rounded_percent}}%)
        </div>
        <CProgress class="my-3">
          <CProgressBar :value="percent" />
        </CProgress>
      </div>
      <div v-if="cur_step == 5">
        <div v-if="!failure && message" class="alert alert-success">
          {{ message }}
        </div>
        <div v-if="failure" class="alert alert-danger">
          {{ failure }}
        </div>
        <button @click="$router.go(-1)" class="btn btn-info prevstep">返回</button>
      </div>
    </div>
    <div>
    </div>

  </div>
</template>

<script>
import GenericSelector from '../components/generic-selector';

export default {
  name: 'CodeBatchOp',
  props: [],
  components: {
    GenericSelector,
  },
  data: function () {
    return {
      percent: 0,
      cur_step: 1,
      range_begin: null,
      range_end: null,
      selection_type: 'code_input',
      batch_op: 'activate',
      nr_affected_rows: null,
      message: null,
      failure: null,
      selected_product_id: null,
      code_str: '',
      code_file: null,
      code_str_maxlength: 1 << 20,
      busy: 0,
    };
  },
  computed: {
    rounded_percent: function() {
      return Math.floor(this.percent * 10) / 10;
    },
    show_affected_rows: function() {
      return this.selection_type != 'code_file';
    },
  },
  methods: {
    set_code_file: async function(e) {
      var fs = e.target.files;
      if (!fs || !fs.length)
        return;
      this.code_file = fs[0];
    },
    nextstep: function() {
      this.cur_step++;
      if (this.cur_step == 3 && this.show_affected_rows) {
        this.fetch_affected_rows();
      }
    },
    get_codes: function(lines) {
      return lines.split(/\r?\n/).filter(this.is_valid_code);
    },
    is_valid_code: function(c) {
      return c.match(/[0-9a-zA-Z]+/);
    },
    read_chunk: async function(f, pos, size) {
      var r = new FileReader();
      var end = pos + size;
      if (end > f.size) end = f.size;
      var b = f.slice(pos, end);
      r.readAsText(b);
      var cb = null;
      var errcb = null;
      r.onload = () => {
        cb(r.result);
      }
      r.onerror = () => {
        errcb();
      }
      return new Promise((accept, reject) => {
        cb = accept;
        errcb = reject;
      });
    },
    iter_line_batches: async function(f, chunk_size, cb) {
      var prev = "";
      for (var pos = 0; pos < f.size; pos += chunk_size) {
        var buf = prev + await this.read_chunk(f, pos, chunk_size);
        var res = buf.split(/\r?\n/);
        var lines;
        if (res.length && res[res.length - 1].length) {
          prev = res[res.length - 1];
          lines = res.slice(0, -1);
        } else {
          prev = '';
          lines = res;
        }
        var end = pos + chunk_size;
        if (end > f.size)
          end = f.size;
        await cb(lines, end * 100 / f.size);
      }
      if (prev.length) {
        await cb([prev], 100);
      }
    },
    foreach_req: async function(dry_run, cb) {
      var req = {};
      var bo = this.batch_op;
      var op = {};
      if (bo == 'bind_product') {
        op.product = this.selected_product_id;
      } else if (bo == 'unbind_product') {
        op.unbind = true;
      } else if (bo == 'activate') {
        op.is_active = true;
      } else if (bo == 'deactivate') {
        op.is_active = false;
      }
      req.operation = op;
      req.dry_run = dry_run;
      var sm = this.selection_type;
      var done = false;
      if (sm == 'seq_range' && this.range_begin) {
        var end = this.range_end;
        if (end == null) {
          end = this.range_begin;
        }
        req.seq_range = [this.range_begin, end];
      } else if (sm == 'code_input') {
        req.codes = this.get_codes(this.code_str);
      } else if (sm == 'code_file') {
        await this.iter_line_batches(this.code_file, 64 << 10, async (lines, pct) => {
          req.codes = lines.filter(this.is_valid_code);
          await cb(req, pct);
        });
        done = true;
      } else if (sm == 'all') {
        req.all = true;
      }
      if (!done) {
        await cb(req, 100);
      }
    },
    run_batch_ops: async function(dry_run) {
      this.percent = 0;
      this.busy++;
      var last_percent = 0;
      try {
        var total = 0;
        await this.foreach_req(dry_run, async (data, pct) => {
          var r = await this.$root.api_post("/api/v1/code-batch-op/", data);
          if (dry_run) {
            total += r.data.count;
          } else if (r.data.job_id) {
            await this.wait_job(r.data.job_id, (job_pct) => {
              console.log(last_percent, pct, job_pct);
              this.percent = last_percent + (pct - last_percent) * job_pct / 100;
            });
          }
          this.percent = pct;
          last_percent = pct;
        });
        this.percent = 100;
      } catch (e) {
        this.failure = e;
      } finally {
        this.busy--
      }
      return total;
    },
    wait_job: async function(job_id, cb) {
      var done = false;
      while (!done) {
        var r = await this.$root.api_get("/api/v1/job/" + job_id + "/");
        var st = r.data.status;
        if (st == 'done') {
          cb(100);
          return;
        }
        if (st != 'running' && st != 'created') {
          throw new Error(r.data.message);
        }
        cb(r.data.progress);
        await this.sleep(2000);
      }
    },
    fetch_affected_rows: async function() {
      this.nr_affected_rows = await this.run_batch_ops(true);
    },
    submit: async function() {
      this.cur_step++;
      await this.run_batch_ops(false);
      this.message = '操作完成!';
      this.nextstep();
    },
  },
  mounted() {
  },
}
</script>

<style scoped>
button.prevstep {
  margin-right: 1rem;
}
</style>

