/*
  Script to parse lines in the format r|w(key,val,session,txn)
  key, val, session, txn are all unsigned integers.
  txns are unique between sessions.
  Checks if a read (r) produces the same value as the last read
  or write for the same key and txn.
*/

#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <format>
#include <stdexcept>
#include <unordered_map>
#include <utility>

using namespace std;

template <typename... Args>
[[noreturn]] void error(format_string<Args...> fmt, Args&&... args) {
    throw runtime_error(format(fmt, std::forward<Args>(args)...));
}

class Parse_context
{
  const char* filename;
  unsigned line_num;
  istringstream input;
  string context()
  {
    return format(" in {}:{}", filename, line_num);
  }
public:
  Parse_context(const char* filename, const string &line, unsigned line_num)
  : filename(filename), line_num(line_num), input(line)
  {
    input.imbue(locale::classic());
  }

  template <typename... Args>
  [[noreturn]] void error(format_string<Args...> fmt, Args&&... args) {
      throw runtime_error(format(fmt, std::forward<Args>(args)...) + context());
  }

  unsigned read_num()
  {
    input >> ws;
    char next= input.peek();
    if (next < '0' || next > '9') error("Number does not start with digit");
    unsigned read_val;
    if(!(input >> read_val)) {
      error("Not a number");
    }
    return read_val;
  }

  char read_char()
  {
    char read_val;
    if(!(input >> read_val)) {
      error("Unexpected end of line");
    }
    return read_val;
  }

  void read_syntax_char(char syntax)
  {
    char c = read_char();
    if (c != syntax) {
      error("Unexpected character '{}' (expected '{}')", c, syntax);
    }
  }

  unsigned read_num_and_syntax(char syntax)
  {
    unsigned read_val = read_num();
    read_syntax_char(syntax);
    return read_val;
  }

  void expect_eol()
  {
    input >> ws;
    if (!input.eof()) error("Extra garbage at end of line");
  }
};

struct Pair_hash {
    unsigned operator()(const pair<unsigned, unsigned> &p) const {
        hash<unsigned> h;
        return h(p.first ^ p.second);
    }
};

void process_file(const char* filename)
{
    ifstream infile(filename);
    using txn_t = unsigned;
    using key_id_t = unsigned;
    using val_t = unsigned;
    unordered_map<pair<txn_t, key_id_t>, val_t, Pair_hash> last_val;
    if (!infile) {
      error("Could not open file {}", filename);
    }
    unsigned line_num{}, ok {}, mismatched {};
    string line;
    while (getline(infile, line)) {
      Parse_context ctx(filename, line, ++line_num);
      char type = ctx.read_char();
      if(type != 'r' && type != 'w') {
        ctx.error("Invalid op type '{}'", type);    
      }
      ctx.read_syntax_char('(');
      key_id_t key = ctx.read_num_and_syntax(',');
      val_t val = ctx.read_num_and_syntax(',');
      [[maybe_unused]] unsigned session = ctx.read_num_and_syntax(',');
      txn_t txn = ctx.read_num_and_syntax(')');
      ctx.expect_eol();
      if (type == 'r') {
        if (auto it = last_val.find({txn, key}); it != last_val.end()) {
          val_t expected = it->second;
          if (val != expected) {
            cout << "Mismatch for txn " << txn
              << ", key " << key << ": read: " << val << " last: " << expected
              << " in file " << filename << "\n";
            ++mismatched;
          } else {
            ++ok;
          }
        }
      }
      last_val[{txn, key}] = val;
    }
    cout << "File " << filename << " checked; reads: "
      << ok << " OK, " << mismatched << " mismatched.\n";
}

int main(int argc, char *argv[])
{
  try
  {
    for(int argn=1; argn<argc; argn++)
    {
      process_file(argv[argn]);
    }
  }
  catch(const exception& e)
  {
    cerr << "Error: " << e.what() << '\n';
    return 1;
  }

  return 0;
}