/*
 * 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/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>
#include <random>

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

/**
 *   Tests of explicit manual rolling of RollingFileAppenders.
 *
 *
 *
 */
LOGUNIT_CLASS(ManualRollingTest)
{
	LOGUNIT_TEST_SUITE(ManualRollingTest);
	LOGUNIT_TEST(test1);
	LOGUNIT_TEST(test2);
	//           TODO: Compression not yet implemented
	//           LOGUNIT_TEST(test3);
	LOGUNIT_TEST(test4);
	LOGUNIT_TEST(test5);
	LOGUNIT_TEST(testShortFileNamePattern);
	LOGUNIT_TEST(create_directories);
	LOGUNIT_TEST_SUITE_END();

	LoggerPtr root;
	LoggerPtr logger;

public:
	void setUp()
	{
		logger = Logger::getLogger("org.apache.log4j.rolling.ManualRollingTest");
		root = Logger::getRootLogger();
	}

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

	void common(RollingFileAppenderPtr & rfa,
		LoggerPtr & logger1)
	{
		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)
			{
				int digit = i % 10;

				if (digit == 0)
				{
					rfa->rollover();
				}

				msg[7] = '0' + i / 10;
				msg[8] = '0' + digit;
			}

			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());
		swrp->setMinIndex(0);

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

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

		common(rfa, logger);

		LOGUNIT_ASSERT_EQUAL(true, File("output/manual-test1.0").exists());
		LOGUNIT_ASSERT_EQUAL(true, File("output/manual-test1.1").exists());
		LOGUNIT_ASSERT_EQUAL(true, File("output/manual-test1.2").exists());
		LOGUNIT_ASSERT_EQUAL(true, Compare::compare(File("output/manual-test1.0"),
				File("witness/rolling/sbr-test2.log")));
		LOGUNIT_ASSERT_EQUAL(true, Compare::compare(File("output/manual-test1.1"),
				File("witness/rolling/sbr-test2.0")));
		LOGUNIT_ASSERT_EQUAL(true, Compare::compare(File("output/manual-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/manual-test2.log"));

		rfa->activateOptions();
		root->addAppender(rfa);

		common(rfa, logger);

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

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

	void testShortFileNamePattern()
	{
		FixedWindowRollingPolicyPtr policy = FixedWindowRollingPolicyPtr(new FixedWindowRollingPolicy());
		policy->setMinIndex(0);
		policy->setMaxIndex(1);
		policy->setFileNamePattern(LOG4CXX_STR("%i"));
		policy->activateOptions();

		Pool pool;
		RolloverDescriptionPtr desc = policy->rollover(
				LOG4CXX_STR("output/manual-short-pattern-active.log"),
				false,
				pool);

		LOGUNIT_ASSERT(desc != NULL);
	}

	/**
	 * 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());

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

		common(rfa, logger);

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

		LOGUNIT_ASSERT_EQUAL(true, Compare::compare(File("output/manual-test3.log"),  File("witness/rolling/sbr-test3.log")));
		LOGUNIT_ASSERT_EQUAL(File("witness/rolling/sbr-test3.0.gz").length(), File("output/manual-test3.0.gz").length());
		LOGUNIT_ASSERT_EQUAL(File("witness/rolling/sbr-test3.1.gz").length(), File("output/manual-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/manual-test4.log"));

		FixedWindowRollingPolicyPtr swrp = FixedWindowRollingPolicyPtr(new FixedWindowRollingPolicy());
		swrp->setCreateIntermediateDirectories(false);
		swrp->setMinIndex(0);

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

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

		common(rfa, logger);

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

		LOGUNIT_ASSERT_EQUAL(true, Compare::compare(File("output/manual-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/manual-test5.log"));

		FixedWindowRollingPolicyPtr swrp = FixedWindowRollingPolicyPtr(new FixedWindowRollingPolicy());

		swrp->setMinIndex(0);

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

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

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


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

		common(rfa, logger);

		os0.close();

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

			LOGUNIT_ASSERT_EQUAL(true, Compare::compare(File("output/manual-test5.log"),
					File("witness/rolling/sbr-test2.log")));
			LOGUNIT_ASSERT_EQUAL(true, Compare::compare(File("output/manual-test5.0"),
					File("witness/rolling/sbr-test2.0")));
			LOGUNIT_ASSERT_EQUAL(true, Compare::compare(File("output/manual-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/manual-test5.log").exists());
			LOGUNIT_ASSERT_EQUAL(true, File("output/manual-test5.0").exists());
			LOGUNIT_ASSERT_EQUAL(true, File("output/manual-test5.2").exists());

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

	void create_directories()
	{
		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/create-directory-file.log"));

		FixedWindowRollingPolicyPtr swrp = FixedWindowRollingPolicyPtr(new FixedWindowRollingPolicy());
		swrp->setMinIndex(0);

		std::random_device dev;
		std::mt19937 rng(dev());
		std::uniform_int_distribution<std::mt19937::result_type> dist(1,100000);
		LogString filenamePattern = LOG4CXX_STR("output/directory-");

		LogString dirNumber;
		StringHelper::toString(static_cast<int>(dist(rng)), dirNumber);

		filenamePattern.append( dirNumber );
		LogString filenamePatternPrefix = filenamePattern;
		filenamePattern.append( LOG4CXX_STR("/file-%i.gz") );
		swrp->setFileNamePattern(filenamePattern);
		swrp->activateOptions();

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

		common(rfa, logger);

		LOGUNIT_ASSERT_EQUAL(true, File(filenamePatternPrefix + LOG4CXX_STR("/file-0.gz")).exists());
		LOGUNIT_ASSERT_EQUAL(true, File(filenamePatternPrefix + LOG4CXX_STR("/file-1.gz")).exists());
	}

};


LOGUNIT_TEST_SUITE_REGISTRATION(ManualRollingTest);
