/*
	This file is part of solidity.
	solidity 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.
	solidity 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 solidity.  If not, see .
*/
// SPDX-License-Identifier: GPL-3.0
#include 
#include 
#include 
using namespace std;
using namespace solidity::yul;
using namespace solidity::phaser;
CacheStats& CacheStats::operator+=(CacheStats const& _other)
{
	hits += _other.hits;
	misses += _other.misses;
	totalCodeSize += _other.totalCodeSize;
	for (auto& [round, count]: _other.roundEntryCounts)
		if (roundEntryCounts.find(round) != roundEntryCounts.end())
			roundEntryCounts.at(round) += count;
		else
			roundEntryCounts.insert({round, count});
	return *this;
}
bool CacheStats::operator==(CacheStats const& _other) const
{
	return
		hits == _other.hits &&
		misses == _other.misses &&
		totalCodeSize == _other.totalCodeSize &&
		roundEntryCounts == _other.roundEntryCounts;
}
Program ProgramCache::optimiseProgram(
	string const& _abbreviatedOptimisationSteps,
	size_t _repetitionCount
)
{
	string targetOptimisations = _abbreviatedOptimisationSteps;
	for (size_t i = 1; i < _repetitionCount; ++i)
		targetOptimisations += _abbreviatedOptimisationSteps;
	size_t prefixSize = 0;
	for (size_t i = 1; i <= targetOptimisations.size(); ++i)
	{
		auto const& pair = m_entries.find(targetOptimisations.substr(0, i));
		if (pair != m_entries.end())
		{
			pair->second.roundNumber = m_currentRound;
			++prefixSize;
			++m_hits;
		}
		else
			break;
	}
	Program intermediateProgram = (
		prefixSize == 0 ?
		m_program :
		m_entries.at(targetOptimisations.substr(0, prefixSize)).program
	);
	for (size_t i = prefixSize + 1; i <= targetOptimisations.size(); ++i)
	{
		string stepName = OptimiserSuite::stepAbbreviationToNameMap().at(targetOptimisations[i - 1]);
		intermediateProgram.optimise({stepName});
		m_entries.insert({targetOptimisations.substr(0, i), {intermediateProgram, m_currentRound}});
		++m_misses;
	}
	return intermediateProgram;
}
void ProgramCache::startRound(size_t _roundNumber)
{
	assert(_roundNumber > m_currentRound);
	m_currentRound = _roundNumber;
	for (auto pair = m_entries.begin(); pair != m_entries.end();)
	{
		assert(pair->second.roundNumber < m_currentRound);
		if (pair->second.roundNumber < m_currentRound - 1)
			m_entries.erase(pair++);
		else
			++pair;
	}
}
void ProgramCache::clear()
{
	m_entries.clear();
	m_currentRound = 0;
}
Program const* ProgramCache::find(string const& _abbreviatedOptimisationSteps) const
{
	auto const& pair = m_entries.find(_abbreviatedOptimisationSteps);
	if (pair == m_entries.end())
		return nullptr;
	return &(pair->second.program);
}
CacheStats ProgramCache::gatherStats() const
{
	return {
		/* hits = */ m_hits,
		/* misses = */ m_misses,
		/* totalCodeSize = */ calculateTotalCachedCodeSize(),
		/* roundEntryCounts = */ countRoundEntries(),
	};
}
size_t ProgramCache::calculateTotalCachedCodeSize() const
{
	size_t size = 0;
	for (auto const& pair: m_entries)
		size += pair.second.program.codeSize(CacheStats::StorageWeights);
	return size;
}
map ProgramCache::countRoundEntries() const
{
	map counts;
	for (auto& pair: m_entries)
		if (counts.find(pair.second.roundNumber) != counts.end())
			++counts.at(pair.second.roundNumber);
		else
			counts.insert({pair.second.roundNumber, 1});
	return counts;
}