import { HZEngineCore } from "../../index.js";

export function conditional(core: HZEngineCore) {
  // if ... [elif] ... [else] ... end if
  // syntax:
  // if <expression>
  // <code>
  // [elif <expression>]
  // <code>
  // [else]
  // <code>
  // end if

  // if statement start
  core.script.use((ctx, next) => {
    if (
      ctx.slicedArgs[0].isQuoted ||
      ctx.slicedArgs[0].str.toLowerCase() !== "if"
    ) {
      return next();
    }
    let data = ctx.startStatement("if") as unknown as IfStatementData;
    // console.log(`if data=${JSON.stringify(data)}`);

    let if_expression_res: unknown = core.script.evalExpression(
      data.if_expression
    );
    if (typeof if_expression_res !== "boolean") {
      throw `If statement: if expression must return boolean, at file [${data.start_position[0]}] line [${data.start_position[1]}]`;
    }
    if (if_expression_res) {
      // continue to execute next line
      return;
    }

    // analyse elif
    for (let i = 0; i < data.elif_list.length; i++) {
      let item = data.elif_list[i];
      let elif_expression_res = core.script.evalExpression(item.expression);
      if (typeof elif_expression_res !== "boolean") {
        throw `If statement: elif expression must return boolean, at file [${item.position[0]}] line [${item.position[1]}]`;
      }
      if (elif_expression_res) {
        // jump to next line of elif statement
        core.script.jump(item.position[0], item.position[1] + 1);
        return;
      }
    }

    // analyse else
    if (data.else_position) {
      core.script.jump(data.else_position[0], data.else_position[1] + 1);
      return;
    }

    // the expressions are all false
    // jump to the end of if statement
    core.script.jump(data.end_position[0], data.end_position[1]);
  });
  // elif statement
  // when come across next elif statement, jump to "end if" statement to stop execute.
  core.script.use((ctx, next) => {
    if (
      ctx.slicedArgs[0].isQuoted ||
      ctx.slicedArgs[0].str.toLowerCase() !== "elif"
    ) {
      return next();
    }
    if (
      ctx.statementStack.length === 0 ||
      ctx.statementStack[ctx.statementStack.length - 1][0] !== "if"
    ) {
      throw `If statement: elif statement must come after if statement, at file [${ctx.currentPath}] line [${ctx.currentLineIndex}]`;
    }
    let data = ctx.statementStack[
      ctx.statementStack.length - 1
    ][2] as unknown as IfStatementData;
    // check if this elif statement belong to the closest if statement
    for (let i = 0; i < data.elif_list.length; i++) {
      let item = data.elif_list[i];
      if (
        item.position[0] === ctx.currentPath &&
        item.position[1] === ctx.currentLineIndex
      ) {
        // jump to the "end if" statement
        core.script.jump(data.end_position[0], data.end_position[1]);
        return;
      }
    }
    // the elif statement is not belong to the closest if statement, so throw error
    throw `If statement: elif statement not belong to the closest if statement, at file [${ctx.currentPath}] line [${ctx.currentLineIndex}]`;
  });
  // else statement
  core.script.use((ctx, next) => {
    if (
      ctx.slicedArgs[0].isQuoted ||
      ctx.slicedArgs[0].str.toLowerCase() !== "else"
    ) {
      return next();
    }
    if (
      ctx.statementStack.length === 0 ||
      ctx.statementStack[ctx.statementStack.length - 1][0] !== "if"
    ) {
      throw `If statement: else statement must come after if statement, at file [${ctx.currentPath}] line [${ctx.currentLineIndex}]`;
    }
    let data = ctx.statementStack[
      ctx.statementStack.length - 1
    ][2] as unknown as IfStatementData;
    // check if this else statement belong to the closest if statement
    if (data.else_position) {
      if (
        data.else_position[0] === ctx.currentPath &&
        data.else_position[1] === ctx.currentLineIndex
      ) {
        // jump to the "end if" statement
        core.script.jump(data.end_position[0], data.end_position[1]);
        return;
      }
    }
    // the else statement is not belong to the closest if statement, so throw error
    throw `If statement: else statement not belong to the closest if statement, at file [${ctx.currentPath}] line [${ctx.currentLineIndex}]`;
  });
  // end if statement
  core.script.use((ctx, next) => {
    if (ctx.rawtext.trim().split(/ +/).join(" ").toLowerCase() !== "end if") {
      return next();
    }
    if (
      ctx.statementStack.length === 0 ||
      ctx.statementStack[ctx.statementStack.length - 1][0] !== "if"
    ) {
      throw `If statement: end if statement must come after if statement, at file [${ctx.currentPath}] line [${ctx.currentLineIndex}]`;
    }
    ctx.endStatement("if");
  });

  // analyse statement
  // if statement
  core.script.useAnalyseStatement((ctx, next) => {
    if (
      ctx.slicedArgs[0].isQuoted ||
      ctx.slicedArgs[0].str.toLowerCase() !== "if"
    ) {
      return next();
    }
    if (ctx.slicedArgs.length < 2) {
      throw `If statement: no expression specified, at file [${ctx.currentPath}] line [${ctx.currentLineIndex}]`;
    }
    ctx.startStatement("if", {
      start_position: [ctx.currentPath, ctx.currentLineIndex],
      if_expression: ctx.rawtext.trim().slice(2).trim(),
      elif_list: [],
      else_position: null,
    });
  });
  // elif statement
  core.script.useAnalyseStatement((ctx, next) => {
    if (
      ctx.slicedArgs[0].isQuoted ||
      ctx.slicedArgs[0].str.toLowerCase() !== "elif"
    ) {
      return next();
    }
    if (ctx.slicedArgs.length < 2) {
      throw `If statement: no expression specified, at file [${ctx.currentPath}] line [${ctx.currentLineIndex}]`;
    }
    // check if the closest statement is if statement
    if (
      ctx.statementStack.length === 0 ||
      ctx.statementStack[ctx.statementStack.length - 1][0] !== "if"
    ) {
      throw `If statement: elif statement must come after if statement, at file [${ctx.currentPath}] line [${ctx.currentLineIndex}]`;
    }
    let data = ctx.statementStack[
      ctx.statementStack.length - 1
    ][2] as unknown as IfStatementData;
    data.elif_list.push({
      position: [ctx.currentPath, ctx.currentLineIndex],
      expression: ctx.rawtext.trim().slice(4).trim(),
    });
  });
  // else statement
  core.script.useAnalyseStatement((ctx, next) => {
    if (
      ctx.slicedArgs[0].isQuoted ||
      ctx.slicedArgs[0].str.toLowerCase() !== "else"
    ) {
      return next();
    }
    // check if the closest statement is if statement
    if (
      ctx.statementStack.length === 0 ||
      ctx.statementStack[ctx.statementStack.length - 1][0] !== "if"
    ) {
      throw `If statement: else statement must come after if statement, at file [${ctx.currentPath}] line [${ctx.currentLineIndex}]`;
    }
    let data = ctx.statementStack[
      ctx.statementStack.length - 1
    ][2] as unknown as IfStatementData;
    data.else_position = [ctx.currentPath, ctx.currentLineIndex];
  });
  // end if statement
  core.script.useAnalyseStatement((ctx, next) => {
    if (ctx.rawtext.trim().split(/ +/).join(" ").toLowerCase() !== "end if") {
      return next();
    }
    let data = ctx.endStatement("if") as unknown as IfStatementData;
    data.end_position = [ctx.currentPath, ctx.currentLineIndex];
    ctx.setStatementData(data, [...data.start_position]);
  });

  // while ... end while

  // while statement start
  core.script.use((ctx, next) => {
    if (
      ctx.slicedArgs[0].isQuoted ||
      ctx.slicedArgs[0].str.toLowerCase() !== "while"
    ) {
      return next();
    }
    let data = ctx.startStatement("while") as unknown as WhileStatementData;
    // test if expression return boolean
    let expression = data.while_expression;
    let expression_res = core.script.evalExpression(expression);
    if (typeof expression_res !== "boolean") {
      throw `While statement: while expression must return boolean, at file [${data.start_position[0]}] line [${data.start_position[1]}]`;
    }
    if (!expression_res) {
      ctx.endStatement("while");
      core.script.jump(...data.end_position, false);
      core.script.incrementNextPosition();
    } else {
      return;
    }
  });

  // while statement end
  core.script.use((ctx, next) => {
    if (
      ctx.rawtext.trim().split(/ +/).join(" ").toLowerCase() !== "end while"
    ) {
      return next();
    }
    // check if the closest statement is while statement
    if (
      ctx.statementStack.length === 0 ||
      ctx.statementStack[ctx.statementStack.length - 1][0] !== "while"
    ) {
      throw `While statement: end while statement must come after while statement, at file [${ctx.currentPath}] line [${ctx.currentLineIndex}]`;
    }
    // jump to while statement start line (loop)
    let data = ctx.statementStack[
      ctx.statementStack.length - 1
    ][2] as unknown as WhileStatementData;
    ctx.endStatement("while");
    core.script.jump(...data.start_position, false);
  });

  // analyse while statement start
  core.script.useAnalyseStatement((ctx, next) => {
    if (
      ctx.slicedArgs[0].isQuoted ||
      ctx.slicedArgs[0].str.toLowerCase() !== "while"
    ) {
      return next();
    }
    let while_expression = ctx.rawtext.trim().slice(5).trim();
    if (while_expression === "") {
      throw `While statement: no expression specified, at file [${ctx.currentPath}] line [${ctx.currentLineIndex}]`;
    }
    ctx.startStatement("while", {
      start_position: [ctx.currentPath, ctx.currentLineIndex],
      while_expression,
      end_position: [ctx.currentPath, ctx.currentLineIndex],
    });
  });

  // analyse while statement end
  core.script.useAnalyseStatement((ctx, next) => {
    if (
      ctx.rawtext.trim().split(/ +/).join(" ").toLowerCase() !== "end while"
    ) {
      return next();
    }
    let data = ctx.endStatement("while") as unknown as WhileStatementData;
    data.end_position = [ctx.currentPath, ctx.currentLineIndex];
    ctx.setStatementData(data, [...data.start_position]);
  });

  // do ... end do until

  // break command
  core.script.use((ctx, next) => {
    if (
      ctx.slicedArgs[0].isQuoted ||
      ctx.slicedArgs.length !== 1 ||
      ctx.slicedArgs[0].str.toLowerCase() !== "break"
    ) {
      return next();
    }

    // find the closest breakable statement
    // (currently only while statement is breakable)

    //(debug) output stack
    // console.log("Stack="+JSON.stringify(ctx.statementStack));

    let resIndex = -1
    for (let i = ctx.statementStack.length - 1; i >= 0; i--) {
      if (ctx.statementStack[i][0] === "while") {
        resIndex = i;
        break;
      }
    }
    if (resIndex === -1) {
      throw `Break command: break statement must come after while statement, at file [${ctx.currentPath}] line [${ctx.currentLineIndex}]`;
    }

    let data = ctx.statementStack[resIndex][2] as unknown as WhileStatementData;
    core.debug.log(`Break command: break from ${ctx.statementStack[resIndex][0]} statement at file [${data.start_position[0]}] line [${data.start_position[1] + 1}]`);
    core.script.jump(
      ...data.end_position,
      false
    );
    core.script.incrementNextPosition();

    // clear statement stack from breakable statement
    ctx.statementStack.splice(resIndex + 1, ctx.statementStack.length - resIndex - 1);
  });
}
interface IfStatementData {
  start_position: [path: string, index: number];
  if_expression: string;
  elif_list: ElifListItem[];
  else_position: [path: string, index: number] | null;
  end_position: [path: string, index: number];
}
interface ElifListItem {
  position: [path: string, index: number];
  expression: string;
}

interface WhileStatementData {
  start_position: [path: string, index: number];
  while_expression: string;
  end_position: [path: string, index: number];
}
