/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include <log4cxx/logstring.h>
#include <log4cxx/rolling/fixedwindowrollingpolicy.h>
#include <log4cxx/helpers/pool.h>
#include <log4cxx/helpers/integer.h>
#include <log4cxx/helpers/stringhelper.h>
#include <log4cxx/helpers/optionconverter.h>
#include <log4cxx/helpers/loglog.h>
#include <log4cxx/helpers/exception.h>
#include <log4cxx/rolling/rolloverdescription.h>
#include <log4cxx/rolling/filerenameaction.h>
#include <log4cxx/rolling/gzcompressaction.h>
#include <log4cxx/rolling/zipcompressaction.h>
#include <log4cxx/pattern/integerpatternconverter.h>
#include <log4cxx/private/rollingpolicybase_priv.h>

using namespace LOG4CXX_NS;
using namespace LOG4CXX_NS::rolling;
using namespace LOG4CXX_NS::helpers;
using namespace LOG4CXX_NS::pattern;

#define priv static_cast<FixedWindowRollingPolicyPrivate*>(m_priv.get())

struct FixedWindowRollingPolicy::FixedWindowRollingPolicyPrivate : public RollingPolicyBasePrivate {
	FixedWindowRollingPolicyPrivate() :
		RollingPolicyBasePrivate(),
		minIndex(1),
		maxIndex(7),
		explicitActiveFile(false)
	{}

	int minIndex;
	int maxIndex;
	bool explicitActiveFile;
	bool throwIOExceptionOnForkFailure = true;
};

IMPLEMENT_LOG4CXX_OBJECT(FixedWindowRollingPolicy)

FixedWindowRollingPolicy::FixedWindowRollingPolicy() :
	RollingPolicyBase (std::make_unique<FixedWindowRollingPolicyPrivate>())
{
}

FixedWindowRollingPolicy::~FixedWindowRollingPolicy(){}

void FixedWindowRollingPolicy::setMaxIndex(int maxIndex1)
{
	priv->maxIndex = maxIndex1;
}

void FixedWindowRollingPolicy::setMinIndex(int minIndex1)
{
	priv->minIndex = minIndex1;
}

void FixedWindowRollingPolicy::setOption(const LogString& option,
	const LogString& value)
{
	if (StringHelper::equalsIgnoreCase(option,
			LOG4CXX_STR("MININDEX"),
			LOG4CXX_STR("minindex")))
	{
		priv->minIndex = OptionConverter::toInt(value, 1);
	}
	else if (StringHelper::equalsIgnoreCase(option,
			LOG4CXX_STR("MAXINDEX"),
			LOG4CXX_STR("maxindex")))
	{
		priv->maxIndex = OptionConverter::toInt(value, 7);
	}
	else if (StringHelper::equalsIgnoreCase(option,
			LOG4CXX_STR("THROWIOEXCEPTIONONFORKFAILURE"),
			LOG4CXX_STR("throwioexceptiononforkfailure")))
	{
		priv->throwIOExceptionOnForkFailure = OptionConverter::toBoolean(value, true);
	}
	else
	{
		RollingPolicyBase::setOption(option, value);
	}
}

/**
 * {@inheritDoc}
 */
void FixedWindowRollingPolicy::activateOptions( LOG4CXX_ACTIVATE_OPTIONS_FORMAL_PARAMETERS )
{
	priv->activateOptions(getFormatSpecifiers());

	if (priv->maxIndex < priv->minIndex)
	{
		LogLog::warn(
			LOG4CXX_STR("MaxIndex  cannot be smaller than MinIndex."));
		priv->maxIndex = priv->minIndex;
	}

	if ((priv->maxIndex - priv->minIndex) > MAX_WINDOW_SIZE)
	{
		LogLog::warn(LOG4CXX_STR("Large window sizes are not allowed."));
		priv->maxIndex = priv->minIndex + MAX_WINDOW_SIZE;
	}

	PatternConverterPtr itc = getIntegerPatternConverter();

	if (itc == NULL)
	{
		throw IllegalStateException();
	}
}

/**
 * {@inheritDoc}
 */
RolloverDescriptionPtr FixedWindowRollingPolicy::initialize( LOG4CXX_ROLLING_POLICY_INITIALIZE_FORMAL_PARAMETERS )
{
	LogString newActiveFile(currentActiveFile);
	priv->explicitActiveFile = false;

	if (currentActiveFile.length() > 0)
	{
		priv->explicitActiveFile = true;
		newActiveFile = currentActiveFile;
	}

	if (!priv->explicitActiveFile)
	{
		LogString buf;
		ObjectPtr obj = std::make_shared<Integer>(priv->minIndex);
		formatFileName(obj, buf);
		newActiveFile = buf;
	}

	ActionPtr noAction;

	return std::make_shared<RolloverDescription>(newActiveFile, append, noAction, noAction);
}

/**
 * {@inheritDoc}
 */
RolloverDescriptionPtr FixedWindowRollingPolicy::rollover( LOG4CXX_ROLLING_POLICY_ROLLOVER_FORMAL_PARAMETERS )
{
	RolloverDescriptionPtr desc;

	if (priv->maxIndex < 0)
	{
		return desc;
	}

	int purgeStart = priv->minIndex;

	if (!priv->explicitActiveFile)
	{
		purgeStart++;
	}

	if (!purge(purgeStart, priv->maxIndex))
	{
		return desc;
	}

	LogString buf;
	ObjectPtr obj = std::make_shared<Integer>(purgeStart);
	formatFileName(obj, buf);

	LogString renameTo(buf);
	LogString compressedName(renameTo);
	ActionPtr compressAction ;

	if(getCreateIntermediateDirectories()){
		File compressedFile(compressedName);
		File compressedParent (compressedFile.getParent());
		compressedParent.mkdirs();
	}

	if (StringHelper::endsWith(renameTo, LOG4CXX_STR(".gz")))
	{
		renameTo.resize(renameTo.size() - 3);
		GZCompressActionPtr comp = std::make_shared<GZCompressAction>(
					File().setPath(renameTo),
					File().setPath(compressedName),
					true);
		comp->setThrowIOExceptionOnForkFailure(priv->throwIOExceptionOnForkFailure);
		compressAction = comp;
	}
	else if (StringHelper::endsWith(renameTo, LOG4CXX_STR(".zip")))
	{
		renameTo.resize(renameTo.size() - 4);
		ZipCompressActionPtr comp = std::make_shared<ZipCompressAction>(
					File().setPath(renameTo),
					File().setPath(compressedName),
					true);
		comp->setThrowIOExceptionOnForkFailure(priv->throwIOExceptionOnForkFailure);
		compressAction = comp;
	}

	auto renameAction = std::make_shared<FileRenameAction>(
				File().setPath(currentActiveFile),
				File().setPath(renameTo),
				false);

	desc = std::make_shared<RolloverDescription>(
				currentActiveFile,  append,
				renameAction,       compressAction);

	return desc;
}

/**
 * Get index of oldest log file to be retained.
 * @return index of oldest log file.
 */
int FixedWindowRollingPolicy::getMaxIndex() const
{
	return priv->maxIndex;
}

/**
 * Get index of most recent log file.
 * @return index of oldest log file.
 */
int FixedWindowRollingPolicy::getMinIndex() const
{
	return priv->minIndex;
}


/**
 * Purge and rename old log files in preparation for rollover
 * @param lowIndex low index
 * @param highIndex high index.  Log file associated with high
 * index will be deleted if needed.
 * @return true if purge was successful and rollover should be attempted.
 */
bool FixedWindowRollingPolicy::purge(int lowIndex, int highIndex) const
{
	int suffixLength = 0;

	std::vector<FileRenameActionPtr> renames;
	LogString buf;
	ObjectPtr obj = std::make_shared<Integer>(lowIndex);
	formatFileName(obj, buf);

	LogString lowFilename(buf);

	if (StringHelper::endsWith(lowFilename, LOG4CXX_STR(".gz")))
	{
		suffixLength = 3;
	}
	else if (StringHelper::endsWith(lowFilename, LOG4CXX_STR(".zip")))
	{
		suffixLength = 4;
	}

	for (int i = lowIndex; i <= highIndex; i++)
	{
		File toRenameCompressed;
		toRenameCompressed.setPath(lowFilename);
		File toRenameBase;
		toRenameBase.setPath(lowFilename.substr(0, lowFilename.length() - suffixLength));
		File* toRename = &toRenameCompressed;
		bool isBase = false;
		bool exists = toRenameCompressed.exists();

		if (suffixLength > 0)
		{
			if (exists)
			{
				if (toRenameBase.exists())
				{
					toRenameBase.deleteFile();
				}
			}
			else
			{
				toRename = &toRenameBase;
				exists = toRenameBase.exists();
				isBase = true;
			}
		}

		if (exists)
		{
			//
			//    if at upper index then
			//        attempt to delete last file
			//        if that fails then abandon purge
			if (i == highIndex)
			{
				if (!toRename->deleteFile())
				{
					return false;
				}

				break;
			}

			//
			//   if intermediate index
			//     add a rename action to the list
			buf.erase(buf.begin(), buf.end());
			obj = std::make_shared<Integer>(i + 1);
			formatFileName(obj, buf);

			LogString highFilename(buf);
			LogString renameTo(highFilename);

			if (isBase)
			{
				renameTo =
					highFilename.substr(0, highFilename.length() - suffixLength);
			}

			renames.push_back(std::make_shared<FileRenameAction>(*toRename, File().setPath(renameTo), true));
			lowFilename = highFilename;
		}
		else
		{
			break;
		}
	}

	//
	//   work renames backwards
	//
	for (std::vector<FileRenameActionPtr>::reverse_iterator iter = renames.rbegin();
		iter != renames.rend();
		iter++)
	{
		auto& action = *iter;
		try
		{
			if (!action->execute())
			{
				return false;
			}
		}
		catch (const std::exception& ex)
		{
			LogLog::warn(action->getName() + LOG4CXX_STR(" raised the following exception"), ex);

			return false;
		}
	}

	return true;
}
#if LOG4CXX_ABI_VERSION <= 15
bool FixedWindowRollingPolicy::purge(int lowIndex, int highIndex, helpers::Pool& pool) const
{
	return purge(lowIndex, highIndex);
}
#endif

#define RULES_PUT(spec, cls) \
	specs.insert(PatternMap::value_type(LogString(LOG4CXX_STR(spec)), (PatternConstructor) cls ::newInstance))


LOG4CXX_NS::pattern::PatternMap FixedWindowRollingPolicy::getFormatSpecifiers() const
{
	PatternMap specs;
	RULES_PUT("i", IntegerPatternConverter);
	RULES_PUT("index", IntegerPatternConverter);
	return specs;
}
