const highlight = (map, source) => {
  let output = "";

  let i = 0;

  while (i < source.length) {
    for (f of map.rules) {
      let p = f((j) => source[i + j], (j) => { i += j });
      if (p != undefined)
        output += p;
    }

    output += source[i++];
  }

  return output;
}

const match = (m) => (peek, take) => {
  for (let i = 0; i < m.length; i++)
    if (peek(i) != m[i])
      return false;
  take(m.length);
  return true;
}
const match_identifier = (m) => (peek, take) => {
  try {
  if (peek(-1).match(/[0-9a-z_]/))
    return false;
  } catch {}
  if (match(m)(peek, take)) {
    if (peek(0).match(/[0-9a-z]/)) {
      take(-m.length);
      return false
    }
    return true;
  }
  return false;
}
const take_until = (p) => (peek, take) => {
  let b = "";
  while (!p()) {
    b += peek(0);
    take(1);
  }
  return b;
}

const simpleHighlight = (c) => (keyword) => (peek, take) => {
  if (match_identifier(keyword)(peek, take))
    return `<span class="${c}">${keyword}</span>`;
}

const keyword = simpleHighlight("keyword");
const altKeyword = simpleHighlight("keyword-alt");

const comment = (prefix) => (peek, take) => {
  if (match(prefix)(peek, take)) {
    let b = prefix;
    b += take_until(() => peek(0) == "\n")(peek, take);
    return `<span class="comment">${b}</span>`
  }
}

const multiline_comment = (prefix, suffix) => (peek, take) => {
  if (match(prefix)(peek, take)) {
    let b = prefix;
    b += take_until(() => match(suffix)(peek, take))(peek, take);
    b += suffix;
    return `<span class="comment">${b}</span>`
  }
}

const string = (c) => (peek, take) => {
  if (match(c)(peek, take)) {
    let b = c;
    b += take_until(() => match(c)(peek, take))(peek, take);
    b += c;
    return `<span class="string">${b}</span>`
  }
}

// keywords: [
//   "auto",
//   "void",
//   "struct"
// ],
// altkeywords: [
//   "const",
//   "static",
//   "int",
//   "std::string",
//   "std::unique_ptr",
//   "char",
//   "public",
//   "private",
//   "protected"
// ],

const maps = {

  cpp: {
    rules: [
      comment("//"),
      multiline_comment("/*", "*/"),

      string("\""),
      // single char
      (peek, take) => {
        if (peek(0) == "'") {
          let b = peek(1);
          take(3);
          return `<span class="string">'${b}'</span>`;
        }
      },

      keyword("auto"),
      keyword("void"),
      keyword("class"),
      keyword("struct"),
      keyword("std::string"),

      altKeyword("const"),
      altKeyword("static"),
      altKeyword("int"),
      altKeyword("char"),
      altKeyword("private"),
      altKeyword("public"),
      altKeyword("protected"),
      altKeyword("abstract"),
      altKeyword("override"),
      altKeyword("return"),
    ],
  },

  nix: {
    rules: [
      comment("#"),

      string("\""),
      string("''"),

      keyword("@"),
      keyword("nixpkgs"),
      keyword("builtins"),

      altKeyword("let"),
      altKeyword("in"),
      altKeyword("with"),
      altKeyword("inherit"),
      altKeyword("import"),
    ],
  },

  rust: {
    rules: [
      comment("//"),

      string("\""),
      // single char
      (peek, take) => {
        if (peek(0) == "'") {
          let b = peek(1);
          take(3);
          return `<span class="string">'${b}'</span>`;
        }
      },

      keyword("fn"),
      keyword("struct"),
      keyword("enum"),
      keyword("trait"),

      keyword("Option"),
      keyword("Some"),
      keyword("None"),
      keyword("Result"),
      keyword("Ok"),
      keyword("Err"),
      keyword("in"),
      keyword("Self"),
      keyword("mut"),

      altKeyword("if"),
      altKeyword("else"),
      altKeyword("match"),
      altKeyword("loop"),
      altKeyword("for"),
      altKeyword("while"),
      altKeyword("return"),
      altKeyword("async"),
      altKeyword("await"),

      altKeyword("mod"),
      altKeyword("use"),
      altKeyword("pub"),
      altKeyword("impl"),
      altKeyword("crate"),
      altKeyword("let"),
      altKeyword("const"),
      altKeyword("static"),

    ]
  }

};
