// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

export default class Assembler {
  static get GENERATOR_ID() { return 0; }

  /**
   * @param {AST} the AST to build the SPIR-V from
   */
  constructor(ast) {
    this.ast_ = ast;
  }

  /**
   * Assembles the AST into binary SPIR-V.
   * @return {Uint32Array} The SPIR-V binary data.
   */
  assemble() {
    let total_size = 5;
    for (const inst of this.ast_.instructions()) {
      total_size += 1;
      for (const op of inst.operands()) {
        total_size += op.length();
      }
    }

    let u = new Uint32Array(total_size);
    u[0] = 0x07230203;  // Magic
    u[1] = 0x00010500;  // Version 1.5
    u[2] = Assembler.GENERATOR_ID;  // Generator magic number
    u[3] = this.ast_.getIdBounds();  // ID bounds
    u[4] = 0;  // Reserved

    let idx = 5;
    for (const inst of this.ast_.instructions()) {
      let op_size = 1;
      for (const op of inst.operands()) {
        op_size += op.length();
      }

      u[idx++] = op_size << 16 | inst.opcode();
      for (const op of inst.operands()) {
        idx = this.processOp(u, idx, op);
      }
    }

    return u;
  }

  processOp(u, idx, op) {
    if (op.type() === "string") {
      let len = 0;
      let v = 0;
      for (const ch of op.value()) {
        v = v | (ch.charCodeAt(0) << (len * 8));
        len += 1;

        if (len === 4) {
          u[idx++] = v;
          len = 0;
          v = 0;
        }
      }
      // Make sure either the terminating 0 byte is written or the last
      // partial word is written.
      u[idx++] = v;

    } else if (op.type() === "float") {
      // TODO(dsinclair): Handle 64 bit floats ...
      let b = new ArrayBuffer(4);
      let f = new Float32Array(b);
      f[0] = op.value();

      let u2 = new Uint32Array(b);

      u[idx++] = u2[0];
    } else {
      u[idx++] = op.value();
    }

    for (const param of op.params()) {
      idx = this.processOp(u, idx, param);
    }

    return idx;
  }
}