const archiver = require('archiver');
const fs = require('fs');
const process = require('process');
const path = require('path');
const sys = require('child_process');

function rm(dst) {
  if(!fs.existsSync(dst)) {
    return;
  }
  let stat = fs.statSync(dst);
  if(stat.isDirectory()) {
    let stack = [];
    let files = [];
    let dirs = [dst];
    let root = fs.readdirSync(dst);

    for (let r of root) {
      let p = path.join(dst, r);
      let stat = fs.statSync(p);
      if (stat.isDirectory()) {
        stack.push(p);
        dirs.push(p);
      } else {
        files.push(p);
      }
    }

    while (stack.length > 0) {
      let p = stack.pop();
      let root = fs.readdirSync(p);
      for (let r of root) {
        let sp = path.join(p, r);
        let stat = fs.statSync(sp);
        if (stat.isDirectory()) {
          stack.push(sp);
          dirs.push(sp);
        } else if (stat.isFile()) {
          files.push(sp);
        }
      }
    }

    for(let f of files) {
      fs.unlinkSync(f);
    }

    dirs.sort((l, r) => {
      let ll = l.length;
      let rl = r.length;
      if (ll < rl) {
        return 1;
      } else if (ll == rl) {
        return 0;
      } else {
        return -1;
      }
    });

    let end = 0;
    for (let i = 0; i < dirs.length; ++i) {
      if (dirs[end] != dirs[i]) {
        dirs[++end] = dirs[i];
      }
    }
    end += 1;
    for (let i = 0; i < end; ++i) {
      if (fs.readdirSync(dirs[i]).length == 0) {
        fs.rmdirSync(dirs[i]);
      }
    }
  }
}

function mv(src, dst) {
  if (!fs.existsSync(src)) {
    throw `${src} is not exists`;
  }

  let lstat = fs.statSync(src);

  if (lstat.isFile()) {
    if (!fs.existsSync(dst)) {
      fs.renameSync(src, dst);
      return;
    }
    if(fs.existsSync(dst)) {
      let rstat = fs.statSync(dst);
      if(rstat.isFile()) {
        fs.unlinkSync(dst);
      }
      else if(rstat.isDirectory()) {
        dst = path.join(dst, path.basename(src));
      }
      else {
        throw `unsupport dst type ${dst}`;
      }
    }
    if(fs.existsSync(dst)) {
      fs.unlinkSync(dst);
    }
    fs.renameSync(src, dst);
    return;
  }

  if (lstat.isDirectory()) {
    if(!fs.existsSync(dst)) {
      fs.mkdirSync(dst, { recursive: true });
    }
    let rstat = fs.statSync(dst);
    if(rstat.isFile()) {
      throw `can't move from directory to a file`;
    }
    if(!rstat.isDirectory()) {
      throw `unsupport dst type ${dst}`;
    }

    let stack = [];
    let files = [];
    let root = fs.readdirSync(src);

    for(let r of root) {
      let p = path.join(src, r);
      let stat = fs.statSync(p);
      if(stat.isDirectory()) {
        stack.push(p);
        files.push([p, true]);
      } else {
        files.push([p, false]);
      }
    }

    while(stack.length > 0) {
      let p = stack.pop();
      let root = fs.readdirSync(p);
      for(let r of root) {
        let sp = path.join(p, r);
        let stat = fs.statSync(sp);
        if(stat.isDirectory()) {
          stack.push(sp);
          files.push([sp, true]);
        } else if(stat.isFile()) {
          files.push([sp, false]);
        }
      }
    }

    let dirs = [];
    src = path.join(path.dirname(src), path.basename(src));
    dirs.push(src);
    for(let f of files) {
      let d = path.join(dst, f[0].substr(src.length));
      let b = "";
      if(f[1]) {
        fs.mkdirSync(d, { recursive: true });
        dirs.push(f[0]);
        b = f[0];
      } else {
        try {
          fs.mkdirSync(path.dirname(d), { recursive: true });
        } catch(e) {
          // ignore error
        }
        fs.renameSync(f[0], d);
        b = path.dirname(f[0]);
      }
    }
    dirs.sort((l, r) => {
      let ll = l.length;
      let rl = r.length;
      if(ll < rl) {
        return 1;
      } else if(ll == rl) {
        return 0;
      } else {
        return -1;
      }
    });

    let end = 0;
    for(let i = 0; i < dirs.length; ++i) {
      if(dirs[end] != dirs[i]) {
        dirs[++end] = dirs[i];
      }
    }
    end += 1;
    for(let i = 0; i < end; ++i) {
      if(fs.readdirSync(dirs[i]).length == 0) {
        fs.rmdirSync(dirs[i]);
      }
    }
    return;
  }

  throw `unsupport src type ${src}`;
}

function cp(src, dst) {
  let copy = (src, dst) => {
    let from = fs.createReadStream(src);
    let to = fs.createWriteStream(dst);
    from.pipe(to);
  };

  if (!fs.existsSync(src)) {
    throw `${src} is not exists`;
  }

  let lstat = fs.statSync(src);

  if (lstat.isFile()) {
    if(!fs.existsSync(dst)) {
      copy(src, dst);
      return;
    }
    if (fs.existsSync(dst)) {
      let rstat = fs.statSync(dst);
      if(rstat.isFile()) {
        fs.unlinkSync(dst);
      }
      else if(rstat.isDirectory()) {
        dst = path.join(dst, path.basename(src));
      }
      else {
        throw `unsupport dst type ${dst}`
      }
    }
    if(fs.existsSync(dst)) {
      fs.unlinkSync(dst);
    }
    copy(src, dst);
    return;
  }

  if (lstat.isDirectory()) {
    if (!fs.existsSync(dst)) {
      fs.mkdirSync(dst, { recursive: true });
    }
    let rstat = fs.statSync(dst);
    if (rstat.isFile()) {
      throw `can't copy from directory to a file`;
    }
    if (!rstat.isDirectory()) {
      throw `unsupport dst type ${dst}`;
    }

    let stack = [];
    let files = [];
    let root = fs.readdirSync(src);

    for (let r of root) {
      let p = path.join(src, r);
      let stat = fs.statSync(p);
      if (stat.isDirectory()) {
        stack.push(p);
      } else if (stat.isFile()) {
        files.push(p);
      }
    }

    while (stack.length > 0) {
      let p = stack.pop();
      let root = fs.readdirSync(p);
      for (let r of root) {
        let sp = path.join(p, r)
        let stat = fs.statSync(sp);
        if (stat.isDirectory()) {
          stack.push(sp);
        } else if (stat.isFile()) {
          files.push(sp);
        }
      }
    }

    src = path.join(path.dirname(src), path.basename(src));
    for (let f of files) {
      let d = path.join(dst, f.substr(src.length));
      try {
        fs.mkdirSync(path.dirname(d), { recursive: true });
      } catch (e) {
        // ignore error
      }
      copy(f, d);
    }
    return;
  }

  throw `unsuppot src type ${src}`;
}

function build_frontend(dst) {
  const oldpath = process.cwd();
  let d = path.join(oldpath, dst);
  process.chdir('frontend');
  sys.execSync('npm install');
  sys.execSync('npm run build');
  cp('dist', path.join(d, 'dist'));
  process.chdir(oldpath);
}

function build_backend(dst) {
  const oldpath = process.cwd();
  let d = path.join(oldpath, dst);
  process.chdir('backend');
  sys.execSync('go build');
  if(process.platform === 'win32') {
    mv('backend.exe', d);
  } else {
    mv('backend', d);
  }
  cp('backend.toml', d);
  process.chdir(oldpath);
}

function build() {
  let d = new Date();
  let base = `mo-${process.platform}-${d.getFullYear()}-${d.getMonth()+1}-${d.getDate()}-${d.getHours()}${d.getMinutes()}${d.getSeconds()}`;
  let dir = 'mo';
  let dst = path.join(dir, base);
  if(fs.existsSync(dir)) {
    rm(dir);
  }
  fs.mkdirSync(dst, { recursive: true });
  build_frontend(dst);
  build_backend(dst);

  let out = `${base}.zip`;
  if(fs.existsSync(out)) {
    fs.unlinkSync(out);
  }
  
  let zip = fs.createWriteStream(out);
  let archive = archiver('zip', {
    zlib: {level: 9}
  });
  zip.on('close', () => {
    rm(dir);
  });
  archive.pipe(zip);
  archive.on('error', err => { throw err; });
  archive.directory(dst, base);
  archive.finalize();
}

build();