#include <iostream>
#include <fstream>
#include <sstream>
#include <stdexcept>
#include <filesystem>

$insert class_ih

$insert namespace-open

    // s_ranges_: use (unsigned) characters as index to obtain
    //           that character's range-number.
    //           The range for EOF is defined in a constant in the
    //           class header file
$insert ranges

$insert startcondinfo
    // transit to if the column's character range was sensed. Row numbers are
    // relative to the used DFA, and d_dfaBase_ is set to the first row of
    // the subset to use.  The row's final two values are respectively the
    // rule that may be matched at this state, and the rule's FINAL flag. If
    // the final value equals FINAL (= 1) then, if there's no continuation,
    // the rule is matched. If the BOL flag (8) is also set (so FINAL + BOL (=
    // 9) is set) then the rule only matches when d_atBOL is also true.
$insert DFAs

$insert DFAbases

size_t \@Base::s_istreamNr = 0;

$insert inputImplementation

\@Base::\@Base(std::istream &in, std::ostream &out, bool keepCwd)
:
    d_out(new std::ostream(out.rdbuf())),
$insert interactiveInit

void \@Base::switchStream_(std::istream &in, size_t lineNr)
{
    d_input->close();
    d_streamStack.back().input = { new std::istream(in.rdbuf()), lineNr };
}

$ignoreInteractive BEGIN    this section is ignored by generator/filter.cc

\@Base::\@Base(std::string const &infilename, std::string const &outfilename, bool keepCwd)
:
    d_out(outfilename == "-"    ? new std::ostream(std::cout.rdbuf()) :
          outfilename == ""     ? new std::ostream(std::cerr.rdbuf()) :
                                  new std::ofstream(outfilename)),
    d_dfaBase_(s_dfa_)
{
    if (keepCwd)
        d_cwd = std::filesystem::current_path().string();

    p_pushStream(infilename, { new std::ifstream(infilename) });
    toCwd();
}

void \@Base::switchStreams(std::istream &in, std::ostream &out)
{
    switchStream_(in, 1);
    switchOstream(out);
}

void \@Base::switchIstream(std::string const &infilename)
{
    d_input->close();
    d_filename = infilename;

    std::ifstream *ptr = new std::ifstream(infilename);

    d_streamStack.back() = 
        StreamStruct{ chgWorkingDir(d_filename), { ptr } };

    d_atBOL = true;
}

void \@Base::switchStreams(std::string const &infilename,
                           std::string const &outfilename)
{
    switchOstream(outfilename);
    switchIstream(infilename);
}

void \@Base::pushStream(std::istream  &istr)
{
    p_pushStream("(istream)", new std::istream(istr.rdbuf())); //streamPtr);
}

void \@Base::pushStream(std::string const &name)
{
    std::istream *streamPtr = new std::ifstream(name);
    if (!*streamPtr)
    {
        delete streamPtr;
        throw std::runtime_error("Cannot read " + name);
    }
    p_pushStream(name, streamPtr);
}

$ignoreInteractive END      end ignored section by generator/filter.cc


void \@Base::toCwd() const
{
    using namespace std;
    using namespace filesystem;

    if (d_cwd.empty())
        return;

    error_code ec;
    current_path(d_cwd, ec);
};

// static
std::string \@Base::chgWorkingDir(std::string const &name)     // any name
{
    using namespace std;
    using namespace filesystem;

    if (name == s_istream)                              // used with istreams
        return name;
                                                        // a filename: get its
    auto path = absolute(name);                         // full pathname

    error_code ec;
    current_path(path.parent_path(), ec);

    return path.string();                               // return the pathname
}

void \@Base::p_pushStream(std::string const &name, std::istream *streamPtr)
{
    if (d_streamStack.size() == s_maxSizeofStreamStack_)
    {
        delete streamPtr;
        throw std::length_error("Max stream stack size exceeded");
    }

    d_filename = name;
    d_streamStack.push_back(
                    StreamStruct{chgWorkingDir(d_filename), { streamPtr } }
                  );

    d_input = &d_streamStack.back().input;

    d_atBOL = true;
}

void \@Base::switchOstream(std::ostream &out)
{
    *d_out << std::flush;
    d_out.reset(new std::ostream(out.rdbuf()));
}

$insert debugFunctions

void \@Base::redo(size_t nChars)
{
    size_t from = nChars >= length() ? 0 : length() - nChars;
    d_input->reRead(d_matched, from);
    d_matched.resize(from);
}

void \@Base::switchOstream(std::string const &outfilename)
{
    *d_out << std::flush;
    d_out.reset(
            outfilename == "-"    ? new std::ostream(std::cout.rdbuf()) :
            outfilename == ""     ? new std::ostream(std::cerr.rdbuf()) :
                                    new std::ofstream(outfilename));
}


bool \@Base::popStream()
{
    using namespace std::filesystem;

    d_input->close();

    if (d_streamStack.size() <= 1)
        return false;

    d_streamStack.pop_back();

    d_filename = path{ chgWorkingDir(d_streamStack.back().pushedName) 
                     }.filename().string();

    d_input = &d_streamStack.back().input;

    return true;
}

$insert lopImplementation


  // See the manual's section `Run-time operations' section for an explanation
  // of this member.
\@Base::ActionType_ \@Base::actionType_(size_t range)
{
    d_nextState = d_dfaBase_[d_state][range];

    if (d_nextState != -1)                  // transition is possible
        return ActionType_::CONTINUE;

    if (knownFinalState())                  // FINAL state reached
        return ActionType_::MATCH;         

    if (d_matched.size())
        return ActionType_::ECHO_FIRST;    // no match, echo the 1st char

    return range != s_rangeOfEOF_ ? 
                ActionType_::ECHO_CH 
            : 
                ActionType_::RETURN;
}

void \@Base::accept(size_t nChars)          // old name: less
{
    if (nChars < d_matched.size())
    {
        d_input->reRead(d_matched, nChars);
        d_matched.resize(nChars);
    }
}

void \@Base::setMatchedSize(size_t length)
{
    d_input->reRead(d_matched, length);  // reread the tail section
    d_matched.resize(length);           // return what's left
}

  // At this point a rule has been matched.  The next character is not part of
  // the matched rule and is sent back to the input.  The final match length
  // is determined, the index of the matched rule is determined, and then
  // d_atBOL is updated. Finally the rule's index is returned.
  // The numbers behind the finalPtr assignments are explained in the 
  // manual's `Run-time operations' section.
size_t \@Base::matched_(size_t ch)
{
$insert 4 debug "MATCH"
    d_input->reRead(ch);

    FinalData *finalPtr;
                            
    if (not d_atBOL)                    // not at BOL
        finalPtr = &d_final.std;        // then use the std rule (3, 4)

                                        // at BOL
    else if (not available(d_final.std.rule))   // only a BOL rule avail.
            finalPtr = &d_final.bol;            // use the BOL rule (6)

    else if (not available(d_final.bol.rule)) // only a std rule is avail.
        finalPtr = &d_final.std;        // use the std rule (7)
        
    else if (                           // Both are available (8)
        d_final.bol.length !=           // check lengths of matched texts
        d_final.std.length              // unequal lengths, use the rule
    )                                   // having the longer match length
        finalPtr =              
            d_final.bol.length > d_final.std.length ?
                &d_final.bol
            :
                &d_final.std;

    else                            // lengths are equal: use 1st rule
        finalPtr = 
            d_final.bol.rule < d_final.std.rule ?
                &d_final.bol
            :
                &d_final.std;

    setMatchedSize(finalPtr->length);

    d_atBOL = d_matched.back() == '\n';

$insert 4 debug "match buffer contains `" << d_matched << "'"

    return finalPtr->rule;
}

size_t \@Base::getRange_(int ch)       // using int to prevent casts
{
$insert caseCheck
    return ch == AT_EOF ? as<size_t>(s_rangeOfEOF_) : s_ranges_[ch];
}

  // At this point d_nextState contains the next state and continuation is
  // possible. The just read char. is appended to d_match
void \@Base::continue_(int ch)
{
$insert 4 debug "CONTINUE, NEXT STATE: " << d_nextState
    d_state = d_nextState;

    if (ch != AT_EOF)
        d_matched += ch;
}

void \@Base::echoCh_(size_t ch)
{
$insert 4 debug "ECHO_CH" 
    *d_out << as<char>(ch);
    d_atBOL = ch == '\n';
}


   // At this point there is no continuation. The last character is
   // pushed back into the input stream as well as all but the first char. in
   // the buffer. The first char. in the buffer is echoed to stderr. 
   // If there isn't any 1st char yet then the current char doesn't fit any
   // rules and that char is then echoed
void \@Base::echoFirst_(size_t ch)
{
$insert 4 debug "ECHO_FIRST"
    d_input->reRead(ch);
    d_input->reRead(d_matched, 1);
    echoCh_(d_matched[0]);
}

    // Update the rules associated with the current state, do this separately
    // for BOL and std rules.
    // If a rule was set, update the rule index and the current d_matched
    // length. 
void \@Base::updateFinals_()
{
    size_t len = d_matched.size();

    int const *rf = d_dfaBase_[d_state] + s_finIdx_;

    if (rf[0] != -1)        // update to the latest std rule
    {
$insert 8 debug "latest std rule: " << rf[0] << ", len = " << len
        d_final.std = FinalData { as<size_t>(rf[0]), len };
    }

    if (rf[1] != -1)        // update to the latest bol rule
    {
$insert 8 debug "latest BOL rule: " << rf[0] << ", len = " << len
        d_final.bol = FinalData { as<size_t>(rf[1]), len };
    }
}

void \@Base::reset_()
{
    d_final = Final{ 
                    FinalData{s_unavailable, 0}, 
                    FinalData {s_unavailable, 0} 
                };

    d_state = 0;
    d_return = true;

    if (!d_more)
        d_matched.clear();

    d_more = false;
}

int \@::executeAction_(size_t ruleIdx)
try
{
$insert 4 debug.R  "Executing actions of rule " << ruleIdx
    switch (ruleIdx)
    {
$insert 8 actions
    }
$insert 4 debug "Rule " << ruleIdx << " did not do 'return'"
    noReturn_();
    return 0;
}
catch (Leave_ value)
{
    return static_cast<int>(value);
}

int \@::lex_()
{
    reset_();
    preCode();

    while (true)
    {
        size_t ch = get_();                // fetch next char
        size_t range = getRange_(ch);      // determine the range

        updateFinals_();                    // update the state's Final info

        switch (actionType_(range))        // determine the action
        {
            case ActionType_::CONTINUE:
                continue_(ch);
            continue;

            case ActionType_::MATCH:
            {
                d_token_ = executeAction_(matched_(ch));
                if (return_())
                {
                    print();
                    toCwd();
                    return d_token_;
                }
                break;
            }

            case ActionType_::ECHO_FIRST:
                echoFirst_(ch);
            break;

            case ActionType_::ECHO_CH:
                echoCh_(ch);
            break;

            case ActionType_::RETURN:
$insert 16 debug  "EOF_REACHED"
                if (!popStream())
                {
                     postCode(PostEnum_::END);
                     toCwd();
                     return 0;
                }
                postCode(PostEnum_::POP);
             continue;
        } // switch

        postCode(PostEnum_::WIP);

        reset_();
        preCode();
    } // while
}

void \@Base::print_() const
{
$insert 4 print
}

$insert interactiveLex

$insert namespace-close
