import { parseArgs } from "util";

let types = {
  u8: {
    c: "unsigned char",
    js: "Uint8",
    size: 1,
  },
  i8: {
    c: "char",
    js: "Int8",
    size: 1,
  },
  u16: {
    c: "unsigned short",
    js: "Uint16",
    size: 2,
  },
  i16: {
    c: "short",
    js: "Int16",
    size: 2,
  },
  u32: {
    c: "unisgned int",
    js: "Uint32",
    size: 4,
  },
  i32: {
    c: "int",
    js: "Int32",
    size: 4,
  },
  f32: {
    c: "float",
    js: "Float32",
    size: 4,
  },
  f64: {
    c: "double",
    js: "Float64",
    size: 8,
  },
};

const { values } = parseArgs({
  args: Bun.argv,
  options: {
    schema: {
      type: "string",
      short: "S",
    },
    jsout: {
        type: "string",
        short: "j"
    },
    cout: {
        type: "string",
        short: "c"
    },
  },
  strict: true,
  allowPositionals: true,
});

function jsStructConstructor(size, containsString) {
  return `
    get bytes() {
        return new Uint8Array(this._ptr);
    }
    constructor(init = {}, ptr = undefined) {
        this._size = ${size};
        this._ptr = ptr.buffer || new ArrayBuffer(this._size);
        this._data = new DataView(this._ptr);
        ${
          containsString
            ? `
        this._encoder = new TextEncoder(); 
        this._decoder = new TextDecoder();`
            : ""
        }          
        for (const key of Object.keys(init)) {
          this[key] = init[key];
        }
    }`;
}

const sFile = Bun.file(values.schema);
const schema = await sFile.json();
let cData = "";

for (const type of Object.keys(schema)) {
  let containsString = false;
  let offset = 0;
  let size = 0;
  let jsData = "";
  const props = schema[type];
  cData += `typedef struct ${type} {`;
  jsData += `class ${type} {`;
  for (const prop of Object.keys(props)) {
    const propType = props[prop].type;
    const kind = props[prop].kind;
    let typeSize = parseInt(types[propType]?.size);

    switch (kind) {
      case "string":
        containsString = true;
        typeSize = props[prop].size;
        const iSize = parseInt(offset) + parseInt(typeSize);
        jsData += `
    get ${prop}() {
        return this._decoder.decode(new Uint8Array(this._ptr.slice(${parseInt(
          offset
        )}, ${iSize})));
    }
    set ${prop}(v) {
        this._data.set(this._encoder.encode(v), ${parseInt(offset)});
    }`;

        cData += `
    char ${prop}[${iSize}];`;
        break;
      case "scalar":
        typeSize = types[propType].size;
        jsData += `
    get ${prop}() {
        return this._data.get${types[propType].js}(${parseInt(offset)}, true);
    }
    set ${prop}(v) {
        return this._data.set${types[propType].js}(${parseInt(
          offset
        )}, v, true);
    }`;

        cData += `
    ${types[propType].c} ${prop};`;

        break;
      case "struct":
        const jsSize = parseInt(offset) + parseInt(types[propType].size);
        jsData += `
    get ${prop}() {
        return new ${propType}({}, new Uint8Array(this._ptr.slice(${offset}, ${jsSize})));
    }
    set ${prop}(v) {
        this._data.set(v.bytes(), ${offset});
    }`;

        cData += `
    ${types[propType].c} ${prop};`;
        break;
      case "array":
        break;
      default:
        break;
    }

    size += parseInt(typeSize);
    offset += parseInt(typeSize);
  }

  /**
   * add the new type to the list so we can use it for structs later.
   */
  types[type] = {
    c: type,
    js: type,
    size: parseInt(size),
  };

  jsData += jsStructConstructor(size, containsString);
  jsData += `
}

export default ${type}`;
  cData += `
} ${type};

`;

  await Bun.write(Bun.file(values.jsout + type + ".js"), jsData);
}
await Bun.write(Bun.file(values.cout + "types.h"), cData);
