// Step 1 -- the parser everyone writes first: getline + string copies.
//
// It is CORRECT (quoted commas, doubled quotes, even newlines inside quoted
// fields -- the case that silently breaks most homemade CSV readers), and it
// is the baseline every later step is measured against. The costs to notice:
//
//   * istream::getline hands us lines, but a CSV record is NOT a line -- a
//     quoted field may contain '\n', so we re-join lines while a quote is
//     open. Record assembly alone forces one std::string append per line.
//   * Every field is copied out of the record into its own std::string:
//     one heap allocation per field, six per row, ~7 million for bench.csv.
//     The allocation counter at the end makes that visible.
//   * std::stod is used to read the value column, the way scripts do.
//
// Build:  c++ -std=c++20 -O2 step1_naive.cpp -o step1
// Run:    ./step1            (expects edge.csv + bench.csv in this directory)
#include "csv_test_common.hpp"
#include <fstream>
#include <string>
#include <vector>

// Split one fully assembled record into unescaped field copies.
static std::vector<std::string> split_record(const std::string& rec) {
    std::vector<std::string> out;
    std::string cur;
    bool in_quotes = false;
    for (size_t i = 0; i < rec.size(); ++i) {
        char c = rec[i];
        if (in_quotes) {
            if (c == '"') {
                if (i + 1 < rec.size() && rec[i + 1] == '"') { cur += '"'; ++i; }
                else in_quotes = false;
            } else cur += c;
        } else {
            if (c == '"') in_quotes = true;
            else if (c == ',') { out.push_back(std::move(cur)); cur.clear(); }
            else cur += c;
        }
    }
    out.push_back(std::move(cur));
    return out;
}

// A record continues onto the next line while it has an odd number of quotes.
static bool quotes_balanced(const std::string& s) {
    size_t n = 0;
    for (char c : s) n += (c == '"');
    return n % 2 == 0;
}

template <typename OnRecord>
static void parse_naive(const char* path, OnRecord on_record) {
    std::ifstream f(path, std::ios::binary);
    std::string line, record;
    while (std::getline(f, line)) {
        record = line;
        while (!quotes_balanced(record) && std::getline(f, line)) {
            record += '\n';           // getline ate the '\n'; the '\r' before
            record += line;           // it is still there -- CRLF survives.
        }
        if (!record.empty() && record.back() == '\r') record.pop_back();
        on_record(split_record(record));
    }
}

static std::vector<std::vector<std::string>> parse_all_materialized(const char* path) {
    std::vector<std::vector<std::string>> rows;
    parse_naive(path, [&](std::vector<std::string> r) { rows.push_back(std::move(r)); });
    return rows;
}

int main() {
    if (!run_edge_checks("edge.csv", parse_all_materialized)) return 1;

    std::ifstream sz("bench.csv", std::ios::binary | std::ios::ate);
    const double mb = sz.tellg() / 1e6;

    BenchResult res;
    for (int rep = 0; rep < 3; ++rep) {
        unsigned long long a0 = allocs_now();
        double t0 = now_secs();
        unsigned long long rows = 0, fields = 0, byte_sum = 0;
        double value_sum = 0.0;
        parse_naive("bench.csv", [&](std::vector<std::string> r) {
            ++rows;
            fields += r.size();
            for (auto& f : r) byte_sum += f.size();
            if (rows > 1 && r.size() > 3) value_sum += std::stod(r[3]);
        });
        double dt = now_secs() - t0;
        if (dt < res.best_secs) res.best_secs = dt;
        res.rows = rows; res.fields = fields; res.byte_sum = byte_sum;
        res.last_pass_allocs = allocs_now() - a0;
        if (rep == 0) std::printf("sum(value) = %.6f\n", value_sum);
    }
    print_bench("step1_naive", res, mb);
    return 0;
}
