// Solutions to the Advent Of Code 2024.
// Copyright (C) 2024-2025  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/HoofIt.hpp>

#include <memory>
#include <queue>
#include <vector>

#include <aoc/common/Grid.hpp>
#include <aoc/common/Point2.hpp>

const std::string HoofIt::getPuzzleName() const
{
    return "Hoof It";
}

const int HoofIt::getPuzzleDay() const
{
    return 10;
}

void HoofIt::finish()
{
    std::queue<Point2> queue{};
    Grid<std::vector<Point2>> trailEndsMap{ lines.size(), lines[0].size() };
    Grid<int> trailScoreMap{ lines.size(), lines[0].size() };

    for (int j = 0; j < lines.size(); j++)
    {
        for (int i = 0; i < lines[j].size(); i++)
        {
            trailScoreMap[j][i] = 0;
            if (lines[j][i] == getTrailTopChar())
            {
                Point2 trailEnd{ i, j };
                trailEndsMap[j][i].push_back(trailEnd);
                trailScoreMap[j][i] = 1;
                queue.push(trailEnd);
            }
        }
    }

    while (!queue.empty())
    {
        const auto trail = queue.front();
        queue.pop();
        if (getCharAt(trail) != getTrailheadChar())
        {
            for (const auto& direction : Point2::cardinalDirections)
            {
                Point2 p{ trail + direction };
                if (isInBounds(p) && getCharAt(p) + 1 == getCharAt(trail))
                {
                    if (trailEndsMap[p.y][p.x].empty())
                    {
                        queue.push(p);
                    }
                    addUnique(trailEndsMap.cell(trail), trailEndsMap.cell(p));
                    trailScoreMap.cell(p) += trailScoreMap.cell(trail);
                }
            }
        }
        else
        {
            part1 += trailEndsMap.cell(trail).size();
            part2 += trailScoreMap.cell(trail);
        }
    }
}

constexpr char HoofIt::getTrailheadChar()
{
    return '0';
}

constexpr char HoofIt::getTrailTopChar()
{
    return '9';
}

void HoofIt::addUnique(const std::vector<Point2>& source, std::vector<Point2>& destination)
{
    for (const auto& end : source)
    {
        auto isUnique{ true };
        size_t i{ 0 };
        while (isUnique && i < destination.size())
        {
            if (end == destination[i])
            {
                isUnique = false;
            }
            i++;
        }
        if (isUnique)
        {
            destination.push_back(end);
        }
    }
}