import * as Commands from "./../../ast/commands";
import { Types } from "./../../typeSystem/types";
import * as Parsers from "./../../typeSystem/parsers";
import { IVProgParser } from "../../ast/ivprogParser";
import { RealLiteral, IntLiteral, BoolLiteral } from "../../ast/expressions";
import { Modes } from "../modes";
import { MultiType } from "../../typeSystem/multiType";
import { ProcessorErrorFactory } from "../error/processorErrorFactory";
import { StoreValue } from "../store/value/store_value";

/**
 *
 * is_real
 * is_int
 * is_bool
 * cast_real
 * cast_int
 * cast_bool
 * cast_string
 * cast_char
 */

export function createIsRealFun () {
  const isRealFun = async (sto, _) => {
    const str = sto.applyStore("str");
    const parser = IVProgParser.createParser(str.get());
    let result = false;
    try {
      const val = parser.parseTerm();
      if (val instanceof RealLiteral) {
        result = true;
      }
    } catch (error) {
      // ignore
    }
    const temp = new StoreValue(Types.BOOLEAN, result);
    sto.insertStore("$", temp);
    sto.mode = Modes.RETURN;
    return sto;
  };

  const block = new Commands.CommandBlock(
    [],
    [new Commands.SysCall(isRealFun)]
  );
  const func = new Commands.Function(
    "$isReal",
    Types.BOOLEAN,
    [new Commands.FormalParameter(Types.STRING, "str", false)],
    block
  );
  return func;
}

export function createIsIntFun () {
  const isIntFun = async (sto, _) => {
    const str = sto.applyStore("str");
    const parser = IVProgParser.createParser(str.get());
    let result = false;
    try {
      const val = parser.parseTerm();
      if (val instanceof IntLiteral) {
        result = true;
      }
    } catch {
      // ignore
    }
    const temp = new StoreValue(Types.BOOLEAN, result);
    sto.insertStore("$", temp);
    sto.mode = Modes.RETURN;
    return sto;
  };

  const block = new Commands.CommandBlock([], [new Commands.SysCall(isIntFun)]);
  const func = new Commands.Function(
    "$isInt",
    Types.BOOLEAN,
    [new Commands.FormalParameter(Types.STRING, "str", false)],
    block
  );
  return func;
}

export function createIsBoolFun () {
  const isBoolFun = (sto, _) => {
    const str = sto.applyStore("str");
    const parser = IVProgParser.createParser(str.get());
    let result = false;
    try {
      const val = parser.parseTerm();
      if (val instanceof BoolLiteral) {
        result = true;
      }
    } catch (error) {
      // ignore
    }
    const temp = new StoreValue(Types.BOOLEAN, result);
    sto.insertStore("$", temp);
    sto.mode = Modes.RETURN;
    return sto;
  };

  const block = new Commands.CommandBlock(
    [],
    [new Commands.SysCall(isBoolFun)]
  );
  const func = new Commands.Function(
    "$isBool",
    Types.BOOLEAN,
    [new Commands.FormalParameter(Types.STRING, "str", false)],
    block
  );
  return func;
}

export function createCastRealFun () {
  const castRealFun = async (sto, _) => {
    const val = sto.applyStore("val");
    let value = val.get();
    switch (val.type.ord) {
      case Types.INTEGER.ord: {
        value = value.toNumber();
        const temp = new StoreValue(Types.REAL, Parsers.toReal(value));
        sto.insertStore("$", temp);
        sto.mode = Modes.RETURN;
        return sto;
      }
      case Types.STRING.ord: {
        const parser = IVProgParser.createParser(value);
        try {
          const result = parser.parseTerm();
          if (result instanceof RealLiteral) {
            const temp = new StoreValue(
              Types.REAL,
              Parsers.toReal(result.value)
            );
            sto.insertStore("$", temp);
            sto.mode = Modes.RETURN;
            return sto;
          }
        } catch (error) {
          // ignore
        }
      }
    }
    const typeStringInfoArray = Types.REAL.stringInfo();
    const typeInfo = typeStringInfoArray[0];
    throw ProcessorErrorFactory.invalid_type_conversion(
      value,
      typeInfo.type,
      typeInfo.dim
    );
  };

  const block = new Commands.CommandBlock(
    [],
    [new Commands.SysCall(castRealFun)]
  );
  const func = new Commands.Function(
    "$castReal",
    Types.REAL,
    [
      new Commands.FormalParameter(
        new MultiType([Types.INTEGER, Types.STRING]),
        "val",
        false
      ),
    ],
    block
  );
  return func;
}

export function createCastIntFun () {
  const castIntFun = async (sto, _) => {
    const val = sto.applyStore("val");
    let value = val.get();
    switch (val.type.ord) {
      case Types.REAL.ord: {
        value = value.toNumber();
        const temp = new StoreValue(
          Types.INTEGER,
          Parsers.toInt(Math.floor(value))
        );
        sto.insertStore("$", temp);
        sto.mode = Modes.RETURN;
        return sto;
      }
      case Types.CHAR.ord: {
        const temp = new StoreValue(
          Types.INTEGER,
          Parsers.toInt(value.charCodeAt(0))
        );
        sto.insertStore("$", temp);
        sto.mode = Modes.RETURN;
        return sto;
      }
      case Types.STRING.ord: {
        const parser = IVProgParser.createParser(value);
        try {
          const result = parser.parseTerm();
          if (result instanceof IntLiteral) {
            const temp = new StoreValue(
              Types.INTEGER,
              Parsers.toInt(result.value)
            );
            sto.insertStore("$", temp);
            sto.mode = Modes.RETURN;
            return sto;
          }
        } catch (error) {
          // ignore
        }
      }
    }
    const typeStringInfoArray = Types.INTEGER.stringInfo();
    const typeInfo = typeStringInfoArray[0];
    throw ProcessorErrorFactory.invalid_type_conversion(
      value,
      typeInfo.type,
      typeInfo.dim
    );
  };

  const block = new Commands.CommandBlock(
    [],
    [new Commands.SysCall(castIntFun)]
  );
  const func = new Commands.Function(
    "$castInt",
    Types.INTEGER,
    [
      new Commands.FormalParameter(
        new MultiType([Types.REAL, Types.STRING, Types.CHAR]),
        "val",
        false
      ),
    ],
    block
  );
  return func;
}

export function createCastBoolFun () {
  const castBoolFun = async (sto, _) => {
    const str = sto.applyStore("str");
    const value = str.get();
    const parser = IVProgParser.createParser(value);
    try {
      const val = parser.parseTerm();
      if (val instanceof BoolLiteral) {
        const temp = new StoreValue(Types.BOOLEAN, val.value);
        sto.insertStore("$", temp);
        sto.mode = Modes.RETURN;
        return sto;
      }
    } catch (error) {
      // ignore
    }
    const typeStringInfoArray = Types.BOOLEAN.stringInfo();
    const typeInfo = typeStringInfoArray[0];
    throw ProcessorErrorFactory.invalid_type_conversion(
      value,
      typeInfo.type,
      typeInfo.dim
    );
  };

  const block = new Commands.CommandBlock(
    [],
    [new Commands.SysCall(castBoolFun)]
  );
  const func = new Commands.Function(
    "$castBool",
    Types.BOOLEAN,
    [new Commands.FormalParameter(Types.STRING, "str", false)],
    block
  );
  return func;
}

export function createCastStringFun () {
  const castStringFun = async function (store, _) {
    const val = store.applyStore("str");
    const result = Parsers.convertToString(val.get(), val.type);
    const temp = new StoreValue(Types.STRING, result);
    store.insertStore("$", temp);
    store.mode = Modes.RETURN;
    return store;
  };
  const block = new Commands.CommandBlock(
    [],
    [new Commands.SysCall(castStringFun)]
  );
  const func = new Commands.Function(
    "$castString",
    Types.STRING,
    [new Commands.FormalParameter(Types.ALL, "str", false)],
    block
  );
  return func;
}

export function createCastCharFun () {
  const castCharFun = (store, _) => {
    const valSV = store.applyStore("charCode");
    // Force the int value to the range [0, 255]
    const value = valSV.get().toNumber() & 0xff;
    const result = String.fromCharCode(value);
    const temp = new StoreValue(Types.CHAR, result);
    store.insertStore("$", temp);
    store.mode = Modes.RETURN;
    return store;
  };
  const block = new Commands.CommandBlock(
    [],
    [new Commands.SysCall(castCharFun)]
  );
  const func = new Commands.Function(
    "$castChar",
    Types.CHAR,
    [new Commands.FormalParameter(Types.INTEGER, "charCode", false)],
    block
  );
  return func;
}

