/*
 * 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 "../util/compare.h"
#include "../insertwide.h"
#include "../logunit.h"
#include <apr_time.h>
#include <log4cxx/basicconfigurator.h>
#include <log4cxx/logmanager.h>
#include <log4cxx/xml/domconfigurator.h>
#include <log4cxx/patternlayout.h>
#include <log4cxx/rolling/fixedwindowrollingpolicy.h>
#include <log4cxx/rolling/sizebasedtriggeringpolicy.h>
#include <log4cxx/filter/levelrangefilter.h>
#include <log4cxx/helpers/pool.h>
#include <log4cxx/logger.h>
#include <log4cxx/propertyconfigurator.h>
#include <log4cxx/rolling/rollingfileappender.h>
#include <log4cxx/helpers/stringhelper.h>
#include <log4cxx/consoleappender.h>
#include <log4cxx/helpers/exception.h>
#include <log4cxx/helpers/fileoutputstream.h>


using namespace log4cxx;
using namespace log4cxx::filter;
using namespace log4cxx::helpers;
using namespace log4cxx::rolling;

/**
 *
 * Do not forget to call activateOptions when configuring programatically.
 *
 *
 *
 */
LOGUNIT_CLASS(SizeBasedRollingTest)
{
	LOGUNIT_TEST_SUITE(SizeBasedRollingTest);
	LOGUNIT_TEST(test1);
	LOGUNIT_TEST(test2);
	LOGUNIT_TEST(test3);
	LOGUNIT_TEST(test4);
	LOGUNIT_TEST(test5);
	LOGUNIT_TEST(test6);
	LOGUNIT_TEST(testShortPatternRegression);
	LOGUNIT_TEST_SUITE_END();

	LoggerPtr root;
	LoggerPtr logger;

public:
	void setUp()
	{
		BasicConfigurator::configure(std::make_shared<PatternLayout> (
			LOG4CXX_STR("%d %level %c -%m%n")
		));
		root = Logger::getRootLogger();
		logger = Logger::getLogger("org.apache.log4j.rolling.SizeBasedRollingTest");
	}

	void tearDown()
	{
		LogManager::shutdown();
	}

	void common(LoggerPtr & logger1, int /*sleep*/)
	{
		char msg[] = { 'H', 'e', 'l', 'l', 'o', '-', '-', '-', 'N', 0 };

		// Write exactly 10 bytes with each log
		for (int i = 0; i < 25; i++)
		{
			if (i < 10)
			{
				msg[8] = '0' + i;
			}
			else if (i < 100)
			{
				msg[7] = '0' + i / 10;
				msg[8] = '0' + i % 10;
			}

			LOG4CXX_DEBUG(logger1, msg);
		}
	}


	/**
	 * Tests that the lack of an explicit active file will use the
	 * low index as the active file.
	 *
	 */
	void test1()
	{
		PatternLayoutPtr layout = PatternLayoutPtr(new PatternLayout(LOG4CXX_STR("%m\n")));
		RollingFileAppenderPtr rfa = RollingFileAppenderPtr(new RollingFileAppender());
		rfa->setName(LOG4CXX_STR("ROLLING"));
		rfa->setAppend(false);
		rfa->setLayout(layout);

		FixedWindowRollingPolicyPtr swrp = FixedWindowRollingPolicyPtr(new FixedWindowRollingPolicy());
		SizeBasedTriggeringPolicyPtr sbtp = SizeBasedTriggeringPolicyPtr(new SizeBasedTriggeringPolicy());

		sbtp->setMaxFileSize(100);
		swrp->setMinIndex(0);

		swrp->setFileNamePattern(LOG4CXX_STR("output/sizeBased-test1.%i"));
		swrp->activateOptions();

		rfa->setRollingPolicy(swrp);
		rfa->setTriggeringPolicy(sbtp);
		rfa->activateOptions();
		root->addAppender(rfa);


		common(logger, 0);

		LOGUNIT_ASSERT_EQUAL(true, File("output/sizeBased-test1.0").exists());
		LOGUNIT_ASSERT_EQUAL(true, File("output/sizeBased-test1.1").exists());
		LOGUNIT_ASSERT_EQUAL(true, File("output/sizeBased-test1.2").exists());
		LOGUNIT_ASSERT_EQUAL(true, Compare::compare(File("output/sizeBased-test1.0"),
				File("witness/rolling/sbr-test2.log")));
		LOGUNIT_ASSERT_EQUAL(true, Compare::compare(File("output/sizeBased-test1.1"),
				File("witness/rolling/sbr-test2.0")));
		LOGUNIT_ASSERT_EQUAL(true, Compare::compare(File("output/sizeBased-test1.2"),
				File("witness/rolling/sbr-test2.1")));
	}

	/**
	 * Test basic rolling functionality with explicit setting of FileAppender.file.
	 */
	void test2()
	{
		PatternLayoutPtr layout = PatternLayoutPtr(new PatternLayout(LOG4CXX_STR("%m\n")));
		RollingFileAppenderPtr rfa = RollingFileAppenderPtr(new RollingFileAppender());
		rfa->setName(LOG4CXX_STR("ROLLING"));
		rfa->setAppend(false);
		rfa->setLayout(layout);
		rfa->setFile(LOG4CXX_STR("output/sizeBased-test2.log"));

		FixedWindowRollingPolicyPtr swrp = FixedWindowRollingPolicyPtr(new FixedWindowRollingPolicy());
		SizeBasedTriggeringPolicyPtr sbtp = SizeBasedTriggeringPolicyPtr(new SizeBasedTriggeringPolicy());

		sbtp->setMaxFileSize(100);
		swrp->setMinIndex(0);

		swrp->setFileNamePattern(LOG4CXX_STR("output/sizeBased-test2.%i"));
		swrp->activateOptions();

		rfa->setRollingPolicy(swrp);
		rfa->setTriggeringPolicy(sbtp);
		rfa->activateOptions();
		root->addAppender(rfa);

		common(logger, 0);

		LOGUNIT_ASSERT_EQUAL(true, File("output/sizeBased-test2.log").exists());
		LOGUNIT_ASSERT_EQUAL(true, File("output/sizeBased-test2.0").exists());
		LOGUNIT_ASSERT_EQUAL(true, File("output/sizeBased-test2.1").exists());

		LOGUNIT_ASSERT_EQUAL(true, Compare::compare(File("output/sizeBased-test2.log"),
				File("witness/rolling/sbr-test2.log")));
		LOGUNIT_ASSERT_EQUAL(true, Compare::compare(File("output/sizeBased-test2.0"),
				File("witness/rolling/sbr-test2.0")));
		LOGUNIT_ASSERT_EQUAL(true, Compare::compare(File("output/sizeBased-test2.1"),
				File("witness/rolling/sbr-test2.1")));
	}

	/**
	 * Same as testBasic but also with GZ compression.
	 */
	void test3()
	{
		PatternLayoutPtr layout = PatternLayoutPtr(new PatternLayout(LOG4CXX_STR("%m\n")));
		RollingFileAppenderPtr rfa = RollingFileAppenderPtr(new RollingFileAppender());
		rfa->setAppend(false);
		rfa->setLayout(layout);

		FixedWindowRollingPolicyPtr  fwrp = FixedWindowRollingPolicyPtr(new FixedWindowRollingPolicy());
		SizeBasedTriggeringPolicyPtr sbtp = SizeBasedTriggeringPolicyPtr(new SizeBasedTriggeringPolicy());

		sbtp->setMaxFileSize(100);
		fwrp->setMinIndex(0);
		rfa->setFile(LOG4CXX_STR("output/sbr-test3.log"));
		fwrp->setFileNamePattern(LOG4CXX_STR("output/sbr-test3.%i.gz"));
		fwrp->activateOptions();
		rfa->setRollingPolicy(fwrp);
		rfa->setTriggeringPolicy(sbtp);
		rfa->activateOptions();
		root->addAppender(rfa);

		common(logger, 100);

		LOGUNIT_ASSERT_EQUAL(true, File("output/sbr-test3.log").exists());
		LOGUNIT_ASSERT_EQUAL(true, File("output/sbr-test3.0.gz").exists());
		LOGUNIT_ASSERT_EQUAL(true, File("output/sbr-test3.1.gz").exists());

		LOGUNIT_ASSERT_EQUAL(true, Compare::compare(File("output/sbr-test3.log"),  File("witness/rolling/sbr-test3.log")));
		LOGUNIT_ASSERT_EQUAL(File("witness/rolling/sbr-test3.0.gz").length(), File("output/sbr-test3.0.gz").length());
		LOGUNIT_ASSERT_EQUAL(File("witness/rolling/sbr-test3.1.gz").length(), File("output/sbr-test3.1.gz").length());
	}

	/**
	 * Test basic rolling functionality with bogus path in file name pattern.
	 */
	void test4()
	{
		PatternLayoutPtr layout = PatternLayoutPtr(new PatternLayout(LOG4CXX_STR("%m\n")));
		RollingFileAppenderPtr rfa = RollingFileAppenderPtr(new RollingFileAppender());
		rfa->setName(LOG4CXX_STR("ROLLING"));
		rfa->setAppend(false);
		rfa->setLayout(layout);
		rfa->setFile(LOG4CXX_STR("output/sizeBased-test4.log"));

		FixedWindowRollingPolicyPtr swrp = FixedWindowRollingPolicyPtr(new FixedWindowRollingPolicy());
		SizeBasedTriggeringPolicyPtr sbtp = SizeBasedTriggeringPolicyPtr(new SizeBasedTriggeringPolicy());
		swrp->setCreateIntermediateDirectories(false);
		sbtp->setMaxFileSize(100);
		swrp->setMinIndex(0);

		//
		//   test4 directory should not exists.  Should cause all rollover attempts to fail.
		//
		swrp->setFileNamePattern(LOG4CXX_STR("output/test4/sizeBased-test4.%i"));
		swrp->activateOptions();

		rfa->setRollingPolicy(swrp);
		rfa->setTriggeringPolicy(sbtp);
		rfa->activateOptions();
		root->addAppender(rfa);

		common(logger, 0);

		LOGUNIT_ASSERT_EQUAL(true, File("output/sizeBased-test4.log").exists());

		LOGUNIT_ASSERT_EQUAL(true, Compare::compare(File("output/sizeBased-test4.log"),
				File("witness/rolling/sbr-test4.log")));
	}

	/**
	 * Checking handling of rename failures due to other access
	 * to the indexed files.
	 */
	void test5()
	{
		PatternLayoutPtr layout = PatternLayoutPtr(new PatternLayout(LOG4CXX_STR("%m\n")));
		RollingFileAppenderPtr rfa = RollingFileAppenderPtr(new RollingFileAppender());
		rfa->setName(LOG4CXX_STR("ROLLING"));
		rfa->setAppend(false);
		rfa->setLayout(layout);
		rfa->setFile(LOG4CXX_STR("output/sizeBased-test5.log"));

		FixedWindowRollingPolicyPtr swrp = FixedWindowRollingPolicyPtr(new FixedWindowRollingPolicy());
		SizeBasedTriggeringPolicyPtr sbtp = SizeBasedTriggeringPolicyPtr(new SizeBasedTriggeringPolicy());
		swrp->setCreateIntermediateDirectories(false);
		sbtp->setMaxFileSize(100);
		swrp->setMinIndex(0);

		swrp->setFileNamePattern(LOG4CXX_STR("output/sizeBased-test5.%i"));
		swrp->activateOptions();

		rfa->setRollingPolicy(swrp);
		rfa->setTriggeringPolicy(sbtp);
		rfa->activateOptions();
		root->addAppender(rfa);

		//
		//   put stray file about locked file
		FileOutputStream os1(LOG4CXX_STR("output/sizeBased-test5.1"), false);
		os1.close();


		FileOutputStream os0(LOG4CXX_STR("output/sizeBased-test5.0"), false);

		common(logger, 0);

		os0.close();

		if (File("output/sizeBased-test5.3").exists())
		{
			//
			//    looks like platform where open files can be renamed
			//
			LOGUNIT_ASSERT_EQUAL(true, File("output/sizeBased-test5.log").exists());
			LOGUNIT_ASSERT_EQUAL(true, File("output/sizeBased-test5.0").exists());
			LOGUNIT_ASSERT_EQUAL(true, File("output/sizeBased-test5.1").exists());
			LOGUNIT_ASSERT_EQUAL(true, File("output/sizeBased-test5.2").exists());
			LOGUNIT_ASSERT_EQUAL(true, File("output/sizeBased-test5.3").exists());

			LOGUNIT_ASSERT_EQUAL(true, Compare::compare(File("output/sizeBased-test5.log"),
					File("witness/rolling/sbr-test2.log")));
			LOGUNIT_ASSERT_EQUAL(true, Compare::compare(File("output/sizeBased-test5.0"),
					File("witness/rolling/sbr-test2.0")));
			LOGUNIT_ASSERT_EQUAL(true, Compare::compare(File("output/sizeBased-test5.1"),
					File("witness/rolling/sbr-test2.1")));

		}
		else
		{
			//
			//  rollover attempts should all fail
			//    so initial log file should have all log content
			//    open file should be unaffected
			//    stray file should have only been moved one slot.
			LOGUNIT_ASSERT_EQUAL(true, File("output/sizeBased-test5.log").exists());
			LOGUNIT_ASSERT_EQUAL(true, File("output/sizeBased-test5.0").exists());
			LOGUNIT_ASSERT_EQUAL(true, File("output/sizeBased-test5.2").exists());

			LOGUNIT_ASSERT_EQUAL(true, Compare::compare(File("output/sizeBased-test5.log"),
					File("witness/rolling/sbr-test4.log")));
		}
	}

	/**
	 * Same as testBasic but also with GZ compression.
	 */
	void test6()
	{
		PatternLayoutPtr layout = PatternLayoutPtr(new PatternLayout(LOG4CXX_STR("%m\n")));
		RollingFileAppenderPtr rfa = RollingFileAppenderPtr(new RollingFileAppender());
		rfa->setAppend(false);
		rfa->setLayout(layout);

		FixedWindowRollingPolicyPtr  fwrp = FixedWindowRollingPolicyPtr(new FixedWindowRollingPolicy());
		SizeBasedTriggeringPolicyPtr sbtp = SizeBasedTriggeringPolicyPtr(new SizeBasedTriggeringPolicy());

		sbtp->setMaxFileSize(100);
		fwrp->setMinIndex(0);
		rfa->setFile(LOG4CXX_STR("output/sbr-test6.log"));
		fwrp->setFileNamePattern(LOG4CXX_STR("output/sbr-test6.%i.zip"));
		fwrp->activateOptions();
		rfa->setRollingPolicy(fwrp);
		rfa->setTriggeringPolicy(sbtp);
		rfa->activateOptions();
		root->addAppender(rfa);

		common(logger, 100);

		LOGUNIT_ASSERT_EQUAL(true, File("output/sbr-test6.log").exists());
		LOGUNIT_ASSERT_EQUAL(true, File("output/sbr-test6.0.zip").exists());
		LOGUNIT_ASSERT_EQUAL(true, File("output/sbr-test6.1.zip").exists());

		LOGUNIT_ASSERT_EQUAL(true, Compare::compare(File("output/sbr-test6.log"),  File("witness/rolling/sbr-test3.log")));
	}

	/**
	 * Regression test for FixedWindowRollingPolicy::purge() underflow.
	 * This test uses a very short fileNamePattern ("R%i" -> "R1") which would
	 * trigger std::out_of_range in the original vulnerable code.
	 */
	void testShortPatternRegression()
	{
		LogString activeFile = LOG4CXX_STR("output/active.log");
		LogString rolledFile = LOG4CXX_STR("R1");
		File(activeFile).deleteFile();
		File(rolledFile).deleteFile();

		PatternLayoutPtr layout = PatternLayoutPtr(new PatternLayout(LOG4CXX_STR("%m\n")));
		RollingFileAppenderPtr rfa = RollingFileAppenderPtr(new RollingFileAppender());
		rfa->setAppend(false);
		rfa->setLayout(layout);
		rfa->setFile(activeFile);

		FixedWindowRollingPolicyPtr swrp = FixedWindowRollingPolicyPtr(new FixedWindowRollingPolicy());
		SizeBasedTriggeringPolicyPtr sbtp = SizeBasedTriggeringPolicyPtr(new SizeBasedTriggeringPolicy());

		sbtp->setMaxFileSize(5); // Very small to trigger rollover
		swrp->setMinIndex(1);
		swrp->setFileNamePattern(LOG4CXX_STR("R%i"));
		swrp->activateOptions();

		rfa->setRollingPolicy(swrp);
		rfa->setTriggeringPolicy(sbtp);
		rfa->activateOptions();
		root->addAppender(rfa);

		// First log: goes to active.log, size becomes > 5
		LOG4CXX_INFO(logger, LOG4CXX_STR("123456"));
		// Second log: triggers rollover
		LOG4CXX_INFO(logger, LOG4CXX_STR("next"));

		// In the vulnerable code, rollover would fail due to std::out_of_range
		// and the rolled file would not exist.
		LOGUNIT_ASSERT(File(rolledFile).exists());

		// Cleanup
		File(activeFile).deleteFile();
		File(rolledFile).deleteFile();
	}

};

LOGUNIT_TEST_SUITE_REGISTRATION(SizeBasedRollingTest);
