#include <iostream>
#include <iomanip>
#include <string>
#include <fstream>
#include <sstream>
#include <limits>

#include <Rcpp.h>

//' Reads lines from a text file with recycling.
//'
// [[Rcpp::export(name = "read.cyclic", rng = false)]]
Rcpp::CharacterVector read_cyclic(
    const std::string& file,
    const int nrows = 1000,
    const int skip = 100,
    const int start = 1,
    const int line_len = 64
) {
    // `unsigned` is not "properly" checked by `Rcpp` to _not_ be negative.
    // An implicit cast to unsigned makes negative integers gigantic, not wanted!
    if (skip < 0) {
        Rcpp::stop("`skip` must be non-negative.");
    }
    if (nrows < 1 || start < 1 || line_len < 1) {
        Rcpp::stop("`start`, `nrows` and `line_len` must be positive.");
    }

    // Open input file
    std::ifstream input(file);
    if (!input) {
        Rcpp::stop("Opening file '%s' failed", file);
    }

    // Handle different versions of start positions, if the start position is
    // large, we guess a value to skip via a simple heuristic and go with it
    if (1000 < start) {
        // Skip (approx) `start` lines
        input.seekg(0, std::ios::end);  // get to end of file
        unsigned long size = static_cast<unsigned long>(input.tellg());
        unsigned long seek = static_cast<unsigned long>(line_len) * (
            static_cast<unsigned long>(start) - 1UL
        );
        seek = seek % size;
        // Now seek to the approx line nr. with recycling
        input.seekg(seek);
        // read till end of line to have a proper start of line position
        if (!input.eof()) {
            input.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        }
        // in the occastional case of ending at a last line (recycle)
        if (input.eof()) { input.seekg(0); }
    } else {
        // Skip (exactly) `start` lines
        for (int line_nr = 1; line_nr < start; ++line_nr) {
            input.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
            // In case of reaching the end of file, restart (recycle)
            if (input.eof()) { input.seekg(0); }
        }
    }

    // Create character vector for output lines
    Rcpp::CharacterVector lines(nrows);

    // Read one line and skip multiple till `nrows` lines are included
    std::string line;
    for (int i = 0; i < nrows; ++i) {
        // Read one line
        if (std::getline(input, line)) {
            lines[i] = line;
        } else {
            // retry from start of file, may occure in last empty line of file
            input.clear();
            input.seekg(0);
            if (std::getline(input, line)) {
                lines[i] = line;
            } else {
                // Another failur is fatal
                Rcpp::stop("Recycling lines in file '%s' failed", file);
            }
        }

        // recycle if at end of file (always check)
        if (input.eof()) { input.seekg(0); }

        // skip lines (with recycling)
        for (int s = 0; s < skip; ++s) {
            input.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
            if (input.eof()) { input.seekg(0); }
        }
    }

    return lines;
}
