import { LanguageService } from "./../services/languageService";
import { LocalizedStrings } from "./../services/localizedStringsService";
import { Operators } from "./../ast/operators";

/**
 * source: https://pawelgrzybek.com/page-scroll-in-vanilla-javascript/
 *
 */
export function scrollIt (
  destination,
  duration = 200,
  easing = "linear",
  callback = null
) {
  const easings = {
    linear (t) {
      return t;
    },
    easeInQuad (t) {
      return t * t;
    },
    easeOutQuad (t) {
      return t * (2 - t);
    },
    easeInOutQuad (t) {
      return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
    },
    easeInCubic (t) {
      return t * t * t;
    },
    easeOutCubic (t) {
      return --t * t * t + 1;
    },
    easeInOutCubic (t) {
      return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;
    },
    easeInQuart (t) {
      return t * t * t * t;
    },
    easeOutQuart (t) {
      return 1 - --t * t * t * t;
    },
    easeInOutQuart (t) {
      return t < 0.5 ? 8 * t * t * t * t : 1 - 8 * --t * t * t * t;
    },
    easeInQuint (t) {
      return t * t * t * t * t;
    },
    easeOutQuint (t) {
      return 1 + --t * t * t * t * t;
    },
    easeInOutQuint (t) {
      return t < 0.5 ? 16 * t * t * t * t * t : 1 + 16 * --t * t * t * t * t;
    },
  };

  const start = window.pageYOffset;
  const startTime =
    "now" in window.performance ? performance.now() : new Date().getTime();

  const documentHeight = Math.max(
    document.body.scrollHeight,
    document.body.offsetHeight,
    document.documentElement.clientHeight,
    document.documentElement.scrollHeight,
    document.documentElement.offsetHeight
  );
  const windowHeight =
    window.innerHeight ||
    document.documentElement.clientHeight ||
    document.getElementsByTagName("body")[0].clientHeight;
  const destinationOffset =
    typeof destination === "number" ? destination : destination.offsetTop;
  const destinationOffsetToScroll = Math.round(
    documentHeight - destinationOffset < windowHeight
      ? documentHeight - windowHeight
      : destinationOffset
  );

  if ("requestAnimationFrame" in window === false) {
    window.scroll(0, destinationOffsetToScroll);
    if (callback) {
      callback();
    }
    return;
  }

  function scroll () {
    const now =
      "now" in window.performance ? performance.now() : new Date().getTime();
    const time = Math.min(1, (now - startTime) / duration);
    const timeFunction = easings[easing](time);
    window.scroll(
      0,
      Math.ceil(timeFunction * (destinationOffsetToScroll - start) + start)
    );

    if (window.pageYOffset === destinationOffsetToScroll) {
      if (callback) {
        callback();
      }
      return;
    }

    requestAnimationFrame(scroll);
  }

  scroll();
}

/**
 *
 * source: https://stackoverflow.com/a/16270434
 */
export function isElementInViewport (el) {
  const rect = el.getBoundingClientRect();

  return (
    rect.bottom > 0 &&
    rect.right > 0 &&
    rect.left < (window.innerWidth || document.documentElement.clientWidth) &&
    rect.top < (window.innerHeight || document.documentElement.clientHeight)
  );
}

let cacheMainList = null;
let cacheOp = null;

function fillCache () {
  if (cacheMainList == null) {
    cacheMainList = [];
    const mainList = [
      "RK_PROGRAM",
      "RK_REAL",
      "RK_VOID",
      "RK_BOOLEAN",
      "RK_STRING",
      "RK_INTEGER",
      "RK_CHARACTER",
      "RK_SWITCH",
      "RK_CASE",
      "RK_DEFAULT",
      "RK_CONST",
      "RK_FUNCTION",
      "RK_RETURN",
      "RK_FOR",
      "RK_BREAK",
      "RK_DO",
      "RK_WHILE",
      "RK_IF",
      "RK_ELSE",
      "RK_FALSE",
      "RK_TRUE",
    ];
    const lexer = LanguageService.getCurrentLexer();
    const names = lexer.getReservedKeys();
    for (let key = 0; key < mainList.length; ++key) {
      const word = mainList[key];
      const keyword = names[word];
      cacheMainList.push(keyword);
    }
  }
  if (cacheOp == null) {
    cacheOp = [];
    const logicOpList = [
      Operators.AND.value,
      Operators.OR.value,
      Operators.NOT.value,
    ];
    for (let op = 0; op < logicOpList.length; ++op) {
      const lOp = `logic_operator_${logicOpList[op]}`;
      cacheOp.push(LocalizedStrings.getUI(lOp));
    }
  }
}

export function isKeyword (text) {
  fillCache();
  for (let key = 0; key < cacheMainList.length; ++key) {
    const keyword = cacheMainList[key];
    if (keyword == text) {
      return true;
    }
  }
  // not in main list, check op
  for (let op = 0; op < cacheOp.length; op++) {
    const lOp = cacheOp[op];
    if (lOp == text) {
      return true;
    }
  }
  return false;
}

export function isValidIdentifier (identifier_str) {
  const validRegex = /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(identifier_str);
  return validRegex && !isKeyword(identifier_str)
}

export function getCodeEditorModeConfig () {
  const blockList = [
    "RK_SWITCH",
    "RK_PROGRAM",
    "RK_CASE",
    "RK_DEFAULT",
    "RK_FOR",
    "RK_FOR_ALT",
    "RK_FUNCTION",
    "RK_DO",
    "RK_WHILE",
    "RK_WHILE_ALT",
    "RK_IF",
    "RK_ELSE",
  ];
  const keywordsList = [
    "RK_CONST",
    "RK_RETURN",
    "RK_BREAK",
    "RK_FOR_FROM",
    "RK_FOR_TO",
    "RK_FOR_PASS",
    "RK_DO_UNTIL",
  ];
  const typeList = [
    "RK_REAL",
    "RK_VOID",
    "RK_BOOLEAN",
    "RK_STRING",
    "RK_INTEGER",
    "RK_CHARACTER",
  ];
  const atomList = ["RK_FALSE", "RK_TRUE"];

  const case_default = [];
  const blocks = [];
  const keywords = [];
  const types = [];
  const atoms = [];
  let switchString = "";

  cacheMainList = [];
  const lexer = LanguageService.getCurrentLexer();
  const names = lexer.getReservedKeys();
  //console.debug(names);
  blockList.forEach((v) => {
    const keyword = names[v];
    const value = keyword;
    cacheMainList.push(value);
    keywords.push(value);
    blocks.push(value);
    if (v == "RK_SWITCH") {
      switchString = value;
    } else if (v == "RK_CASE" || v == "RK_DEFAULT") {
      case_default.push(value);
    }
  });
  keywordsList.forEach((v) => {
    const keyword = names[v];
    const value = keyword;
    cacheMainList.push(value);
    keywords.push(value);
  });
  typeList.forEach((v) => {
    const keyword = names[v];
    const value = keyword;
    cacheMainList.push(value);
    types.push(value);
  });
  atomList.forEach((v) => {
    const keyword = names[v];
    const value = keyword;
    cacheMainList.push(value);
    atoms.push(value);
  });

  cacheOp = [];
  const logicOpList = ["NOT_OPERATOR", "OR_OPERATOR", "AND_OPERATOR"];

  logicOpList.forEach((v) => {
    const keyword = names[v];
    const value = keyword;
    cacheOp.push(value);
    keywords.push(value);
  });
  cacheMainList.push(lexer.getLangFuncs().main_function);
  return {
    case_default: case_default,
    atoms: atoms,
    keywords: keywords,
    switchString: switchString,
    types: types,
    blocks: blocks,
  };
}

/**
 * Source: https://gist.github.com/andrei-m/982927
 * @param {string} a
 * @param {string} b
 */
export function levenshteinDistance (a, b) {
  if (a.length == 0) return b.length;
  if (b.length == 0) return a.length;

  const matrix = [];

  // increment along the first column of each row
  let i;
  for (i = 0; i <= b.length; i++) {
    matrix[i] = [i];
  }

  // increment each column in the first row
  let j;
  for (j = 0; j <= a.length; j++) {
    matrix[0][j] = j;
  }

  // Fill in the rest of the matrix
  for (i = 1; i <= b.length; i++) {
    for (j = 1; j <= a.length; j++) {
      if (b.charCodeAt(i - 1) == a.charCodeAt(j - 1)) {
        matrix[i][j] = matrix[i - 1][j - 1];
      } else {
        matrix[i][j] = Math.min(
          matrix[i - 1][j - 1] + 1, // substitution
          Math.min(
            matrix[i][j - 1] + 1, // insertion
            matrix[i - 1][j] + 1
          )
        ); // deletion
      }
    }
  }
  return matrix[b.length][a.length];
}

let win = null;
export function openAssessmentDetail (event) {
  event.preventDefault();
  const page_code = event.currentTarget.dataset.page;
  if (win != null) {
    win.close();
  }
  win = window.open("", "DetailWindow", "width=550,height=600");
  win.document.open();
  win.document.write(page_code);
  win.document.close();
}

export function range (size, startAt = 0) {
  return [...Array(size).keys()].map((i) => i + startAt);
}

/**
 *
 * @param {number} ms
 */
export async function sleep (ms) {
  return new Promise((res, _) => setTimeout(res, ms));
}