import * as Commands from "./../../ast/commands";
import { Modes } from "../modes";
import * as Parsers from "./../../typeSystem/parsers";
import { Types } from "./../../typeSystem/types";
import { ProcessorErrorFactory } from "./../error/processorErrorFactory";
import { StoreValue } from "../store/value/store_value";

export function createOutputFun () {
  const writeFunction = async function (store, _) {
    const totalSV = store.applyStore("p1.0");
    const total = totalSV.get();
    for (let i = 1; i < total; i += 1) {
      const val = store.applyStore(`p1.${i}`);
      this.output.sendOutput(Parsers.convertToString(val.get(), val.type));
    }
    store.mode = Modes.RETURN;
    return store;
  };
  const block = new Commands.CommandBlock(
    [],
    [new Commands.SysCall(writeFunction)]
  );
  const func = new Commands.Function(
    "$write",
    Types.VOID,
    [new Commands.FormalParameter(Types.ALL, "p1", false, true)],
    block
  );
  return func;
}

export function createInputFun () {
  const readFunction = async function (store, _) {
    const text = await this.input.requestInput();
    const typeToConvert = store.applyStore("p1").type;
    let type = null;
    let result = null;
    try {
      if (typeToConvert.isCompatible(Types.INTEGER)) {
        result = Parsers.toInt(text.trim()).trunc();
        type = Types.INTEGER;
      } else if (typeToConvert.isCompatible(Types.REAL)) {
        result = Parsers.toReal(text.trim());
        type = Types.REAL;
      } else if (typeToConvert.isCompatible(Types.BOOLEAN)) {
        result = Parsers.toBool(text.trim());
        type = Types.BOOLEAN;
      } else if (typeToConvert.isCompatible(Types.STRING)) {
        result = Parsers.toString(text);
        type = Types.STRING;
      } else if (typeToConvert.isCompatible(Types.CHAR)) {
        result = Parsers.toChar(text);
        type = Types.CHAR;
      } else {
        throw new Error("!!!!Critical error: Unknown type in readFunction!!!!");
      }
    } catch (_) {
      if (this.mode == Modes.ABORT) {
        store.mode = Modes.RETURN;
        return store;
      }
      const stringInfo = typeToConvert.stringInfo()[0];
      const realObject = store.getStoreObject("p1");
      if (realObject.getReferenceDimension() > 0) {
        const arrayInfo = realObject.type.stringInfo()[0];
        const dim = realObject.getReferenceDimension();
        throw ProcessorErrorFactory.invalid_read_type_array(
          text,
          stringInfo.type,
          stringInfo.dim,
          realObject.getRefObj(),
          arrayInfo.type,
          dim,
          this.function_call_stack.pop()
        );
      }
      throw ProcessorErrorFactory.invalid_read_type(
        text,
        stringInfo.type,
        stringInfo.dim,
        realObject.getRefObj(),
        this.function_call_stack.pop()
      );
    }
    const stoValue = new StoreValue(type, result);
    store.updateStore("p1", stoValue);
    store.mode = Modes.RETURN;
    return store;
  };
  const block = new Commands.CommandBlock(
    [],
    [new Commands.SysCall(readFunction)]
  );
  const func = new Commands.Function(
    "$read",
    Types.VOID,
    [new Commands.FormalParameter(Types.ALL, "p1", true)],
    block
  );
  return func;
}
