// Solutions to the Advent Of Code 2024.
// Copyright (C) 2024  Stefan Müller
//
// This program is free software: you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free Software
// Foundation, either version 3 of the License, or (at your option) any later
// version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with
// this program.  If not, see <http://www.gnu.org/licenses/>.

#include <aoc/RedNosedReports.hpp>

#include <sstream>
#include <vector>

const std::string RedNosedReports::getPuzzleName() const
{
    return "Day 2: Red-Nosed Reports";
}

const std::string RedNosedReports::getInputFileName() const
{
    return "red-nosed_reports.txt";
}

// 'X' in the comments here and below marks the level that must be ignored to make the report safe.
// 'first' means that this series starts at the beginning of the line.
// 'next' means that the level(s) from the next loop iteration(s) will determine which level to ignore.
void RedNosedReports::processDataLine(const std::string& line)
{
    RedNosedReportData data{};

    std::stringstream stream{ line };
    std::string token;
    std::getline(stream, token, ' ');
    data.levels.push_back(std::stoi(token));

    while (data.isSafe && std::getline(stream, token, ' '))
    {
        do
        {
            data.levels.push_back(std::stoi(token));
        } while (data.mustAwaitFourLevels && data.levels.size() < 4
            && std::getline(stream, token, ' '));

        if (data.mustCheckSlopeReversal)
        {
            auto it = data.levels.rbegin();
            if (data.slope == Slope::Decreasing && *it > *(++it))
            {
                //         X
                // first 3 2 6 7
                data.slope = Slope::Increasing;
            }
            else if (data.slope == Slope::Increasing && *it < *(++it))
            {
                //         X
                // first 6 7 3 2
                data.slope = Slope::Decreasing;
            }
            else
            {
                //           X
                // first 3 2 6 1
                // first 6 7 3 8
                data.mustSkipPrevious = true;
            }
        }
        data.mustCheckSlopeReversal = false;

        auto it = data.levels.rbegin();
        auto delta{ *it };
        delta -= *(++(data.mustSkipPrevious ? ++it : it));
        data.mustSkipPrevious = false;

        if (delta == 0)
        {
            // X
            // 1 1
            data.isSafe = data.canUseDampener;
            data.canUseDampener = false;
        }
        else if (delta > 0)
        {
            checkLastLevel(Slope::Increasing, Slope::Decreasing, delta, 1, data);
        }
        else
        {
            checkLastLevel(Slope::Decreasing, Slope::Increasing, -delta, -1, data);
        }
    }
    if (data.isSafe)
    {
        if (data.canUseDampener)
        {
            part1++;
        }
        part2++;
    }
}

void RedNosedReports::finish()
{
}

void RedNosedReports::checkLastLevel(const Slope sameSlope, const Slope otherSlope, const int delta, const int sign,
    RedNosedReportData& data)
{
    if (data.slope == sameSlope)
    {
        if (delta > 3)
        {
            //     X            X
            // 1 2 6        8 7 3
            data.mustSkipPrevious = true;
            data.isSafe = data.canUseDampener;
            data.canUseDampener = false;
        }
    }
    else if (data.slope == otherSlope)
    {
        if (data.levels.size() == 3 && sign * (data.levels[2] - data.levels[0]) <= 3)
        {
            //         X X next                X X next
            // first 3 2 6 ???         first 6 7 3 ???
            data.mustCheckSlopeReversal = true;
        }
        else
        {
            //           X                  X
            // first 3 2 7        first 6 7 2
            //     3 2 1 4            6 7 8 5
            data.mustSkipPrevious = true;
        }
        data.isSafe = data.canUseDampener;
        data.canUseDampener = false;
    }
    else // slope == Slope::Unknown
    {
        if (delta <= 3)
        {
            data.slope = sameSlope;
        }
        if (data.mustAwaitFourLevels)
        {
            if (delta <= 3 &&
                ((0 < sign * (data.levels[2] - data.levels[0]) && sign * (data.levels[2] - data.levels[0]) <= 3) ||
                    (0 < sign * (data.levels[2] - data.levels[1]) && sign * (data.levels[2] - data.levels[1]) <= 3)))
            {
                //       X X                  X X
                // first 1 5 3 4        first 8 4 6 5
                data.mustAwaitFourLevels = false;
            }
            else
            {
                data.isSafe = false;
            }
        }
        else if (delta > 3)
        {
            //       X X next              X X next
            // first 1 5 ???         first 8 4 ???
            data.mustAwaitFourLevels = true;
            data.isSafe = data.canUseDampener;
            data.canUseDampener = false;
        }
    }
}