/*
 * 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/logger.h>
#include <log4cxx/xml/domconfigurator.h>
#include <log4cxx/defaultconfigurator.h>
#include "../logunit.h"
#include "../util/compare.h"
#include "xlevel.h"
#include "../util/controlfilter.h"
#include "../util/iso8601filter.h"
#include "../util/threadfilter.h"
#include "../util/transformer.h"
#include <iostream>
#include <log4cxx/file.h>
#include <log4cxx/fileappender.h>
#include <apr_pools.h>
#include <apr_file_io.h>
#include "../testchar.h"
#include "log4cxx/helpers/loglog.h"
#include <log4cxx/helpers/filesystempath.h>
#include "log4cxx/helpers/pool.h"

using namespace log4cxx;
using namespace log4cxx::helpers;
using namespace log4cxx::xml;

#define TEST1_1A_PAT \
	"(DEBUG|INFO |WARN |ERROR|FATAL) \\w*\\.\\w* - Message [0-9]"

#define TEST1_1B_PAT "(DEBUG|INFO |WARN |ERROR|FATAL) root - Message [0-9]"

#define TEST1_2_PAT "^[0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} [0-9]\\{2\\}:[0-9]\\{2\\}:[0-9]\\{2\\},[0-9]\\{3\\} " \
	"\\[0x[0-9A-F]*]\\ (DEBUG|INFO|WARN|ERROR|FATAL) .* - Message [0-9]"

LOGUNIT_CLASS(DOMTestCase)
{
	LOGUNIT_TEST_SUITE(DOMTestCase);
	LOGUNIT_TEST(test1);
#if defined(_WIN32)
	LOGUNIT_TEST(test2);
#endif
	LOGUNIT_TEST(test3);
	LOGUNIT_TEST(test4);
	LOGUNIT_TEST(recursiveAppenderRef);
	LOGUNIT_TEST(invalidAppender);
	LOGUNIT_TEST(invalidLevel);
	LOGUNIT_TEST(testAutoFallback);
	LOGUNIT_TEST_SUITE_END();

	LoggerPtr root;
	LoggerPtr logger;
	LogString output_dir;

public:
	void setUp()
	{
		LogLog::setInternalDebugging(true);
#if !LOG4CXX_HAS_FILESYSTEM_PATH
		spi::Configurator::properties().setProperty(LOG4CXX_STR("PROGRAM_FILE_PATH.PARENT_PATH"), LOG4CXX_STR("output"));
#endif
		output_dir = spi::Configurator::properties().getProperty(LOG4CXX_STR("PROGRAM_FILE_PATH.PARENT_PATH"));

		root = Logger::getRootLogger();
		logger = Logger::getLogger(LOG4CXX_TEST_STR("org.apache.log4j.xml.DOMTestCase"));
	}

	void tearDown()
	{
		auto rep = root->getLoggerRepository();

		if (rep)
		{
			rep->resetConfiguration();
		}
	}

	void test1()
	{
		auto status = DOMConfigurator::configure(LOG4CXX_TEST_STR("input/xml/DOMTestCase1.xml"));
		LOGUNIT_ASSERT_EQUAL(status, spi::ConfigurationStatus::Configured);

		// Check that ${PROGRAM_FILE_PATH.PARENT_PATH} is expanded
		auto fa = LOG4CXX_NS::cast<FileAppender>(root->getAppender(LOG4CXX_STR("A1")));
		LOGUNIT_ASSERT(fa);
		File logFile{ fa->getFile() };
		LOGUNIT_ASSERT(!output_dir.empty());
		LOGUNIT_ASSERT_EQUAL(output_dir, logFile.getParent());
		common();

		ControlFilter cf1;
		cf1 << TEST1_1A_PAT << TEST1_1B_PAT;

		ControlFilter cf2;
		cf2 << TEST1_2_PAT;

		ThreadFilter threadFilter;
		ISO8601Filter iso8601Filter;

		std::vector<Filter*> filters1;
		filters1.push_back(&cf1);

		std::vector<Filter*> filters2;
		filters2.push_back(&cf2);
		filters2.push_back(&threadFilter);
		filters2.push_back(&iso8601Filter);

		const File TEMP_A1{ output_dir + LOG4CXX_STR("/temp.A1") };
		const File FILTERED_A1{ output_dir + LOG4CXX_STR("/filtered.A1") };
		const File TEMP_A2{ output_dir + LOG4CXX_STR("/temp.A2") };
		const File FILTERED_A2{ output_dir + LOG4CXX_STR("/filtered.A2") };

		try
		{
			Transformer::transform(TEMP_A1, FILTERED_A1, filters1);
			Transformer::transform(TEMP_A2, FILTERED_A2, filters2);
		}
		catch (UnexpectedFormatException& e)
		{
			std::cout << "UnexpectedFormatException :" << e.what() << std::endl;
			throw;
		}

		const File witness1(LOG4CXX_TEST_STR("witness/dom.A1.1"));
		const File witness2(LOG4CXX_TEST_STR("witness/dom.A2.1"));
		LOGUNIT_ASSERT(Compare::compare(FILTERED_A1, witness1));
		LOGUNIT_ASSERT(Compare::compare(FILTERED_A2, witness2));
	}

	//
	// Same test but backslashes instead of forward
	//
	void test2()
	{
		auto status = DOMConfigurator::configure(LOG4CXX_TEST_STR("input\\xml\\DOMTestCase2.xml"));
		LOGUNIT_ASSERT_EQUAL(status, spi::ConfigurationStatus::Configured);
		common();

		ThreadFilter threadFilter;
		ISO8601Filter iso8601Filter;

		std::vector<Filter*> filters1;

		std::vector<Filter*> filters2;
		filters2.push_back(&threadFilter);
		filters2.push_back(&iso8601Filter);

		const File TEMP_A1_2{ output_dir + LOG4CXX_STR("/temp.A1.2") };
		const File TEMP_A2_2{ output_dir + LOG4CXX_STR("/temp.A2.2") };
		const File FILTERED_A1_2{ output_dir + LOG4CXX_STR("/filtered.A1.2") };
		const File FILTERED_A2_2{ output_dir + LOG4CXX_STR("/filtered.A2.2") };
		try
		{
			Transformer::transform(TEMP_A1_2, FILTERED_A1_2, filters1);
			Transformer::transform(TEMP_A2_2, FILTERED_A2_2, filters2);
		}
		catch (UnexpectedFormatException& e)
		{
			std::cout << "UnexpectedFormatException :" << e.what() << std::endl;
			throw;
		}

		const File witness1(LOG4CXX_TEST_STR("witness/dom.A1.2"));
		LOGUNIT_ASSERT(Compare::compare(FILTERED_A1_2, witness1));
	}


	void common()
	{
		int i = 0;

		LOG4CXX_DEBUG(logger, "Message " << i);
		LOG4CXX_DEBUG(root, "Message " << i);

		i++;
		LOG4CXX_INFO(logger, "Message " << i);
		LOG4CXX_INFO(root, "Message " << i);

		i++;
		LOG4CXX_WARN(logger, "Message " << i);
		LOG4CXX_WARN(root, "Message " << i);

		i++;
		LOG4CXX_ERROR(logger, "Message " << i);
		LOG4CXX_ERROR(root, "Message " << i);

		i++;
		LOG4CXX_FATAL(logger, "Message " << i);
		LOG4CXX_FATAL(root, "Message " << i);
	}

	/**
	 * Creates a output file that ends with a superscript 3.
	 */
	void test3()
	{
		auto status = DOMConfigurator::configure(LOG4CXX_TEST_STR("input/xml/DOMTestCase3.xml"));
		LOGUNIT_ASSERT_EQUAL(status, spi::ConfigurationStatus::Configured);
		LOG4CXX_INFO(logger, "File name is expected to end with a superscript 3");
#if LOG4CXX_LOGCHAR_IS_UTF8
		const logchar fname[] = { 0x6F, 0x75, 0x74, 0x70, 0x75, 0x74, 0x2F, 0x64, 0x6F, 0x6D, static_cast<logchar>(0xC2), static_cast<logchar>(0xB3), 0 };
#else
		const logchar fname[] = { 0x6F, 0x75, 0x74, 0x70, 0x75, 0x74, 0x2F, 0x64, 0x6F, 0x6D, static_cast<logchar>(0xB3), 0 };
#endif
		;
		bool exists = File(fname).exists();
		LOGUNIT_ASSERT(exists);
	}

	/**
	 * Creates a output file that ends with a ideographic 4.
	 */
	void test4()
	{
		auto status = DOMConfigurator::configure(LOG4CXX_TEST_STR("input/xml/DOMTestCase4.xml"));
		LOGUNIT_ASSERT_EQUAL(status, spi::ConfigurationStatus::Configured);
		LOG4CXX_INFO(logger, "File name is expected to end with an ideographic 4");
#if LOG4CXX_LOGCHAR_IS_UTF8
		const logchar fname[] = { 0x6F, 0x75, 0x74, 0x70, 0x75, 0x74, 0x2F, 0x64, 0x6F, 0x6D, static_cast<logchar>(0xE3), static_cast<logchar>(0x86), static_cast<logchar>(0x95), 0 };
#else
		const logchar fname[] = { 0x6F, 0x75, 0x74, 0x70, 0x75, 0x74, 0x2F, 0x64, 0x6F, 0x6D, static_cast<logchar>(0x3195), 0 };
#endif
		bool exists = File(fname).exists();
		LOGUNIT_ASSERT(exists);
	}


	void recursiveAppenderRef()
	{
		// Load a bad XML file, make sure that we don't crash in endless recursion
		auto status = DOMConfigurator::configure(LOG4CXX_TEST_STR("input/xml/DOMConfiguratorRecursive.xml"));
		LOGUNIT_ASSERT_EQUAL(status, spi::ConfigurationStatus::NotConfigured);
	}

	void invalidAppender()
	{
		// Load an XML file that attempts to use a levelmatchfilter as an appender.
		// We should not crash when loading this file.
		auto status = DOMConfigurator::configure(LOG4CXX_TEST_STR("input/xml/DOMInvalidAppender.xml"));
		LOGUNIT_ASSERT_EQUAL(status, spi::ConfigurationStatus::NotConfigured);
	}
  
	void invalidLevel()
	{
		// Load an XML file that attempts to use a filter as a level.
		// We should not crash when loading this file.
		auto status = DOMConfigurator::configure(LOG4CXX_TEST_STR("input/xml/DOMInvalidLevel.xml"));
		LOGUNIT_ASSERT_EQUAL(status, spi::ConfigurationStatus::Configured);
	}

	void testAutoFallback()
	{
		std::vector<LogString> paths
			{ LOG4CXX_STR("input/xml")
			};
		std::vector<LogString> names
			{ LOG4CXX_STR("DOMTestCase5_bad0.xml")
			, LOG4CXX_STR("DOMTestCase5_bad1.xml")
			, LOG4CXX_STR("DOMTestCase5_bad2.xml")
			, LOG4CXX_STR("DOMTestCase5_bad3.xml")
			, LOG4CXX_STR("DOMTestCase5_good.xml")
			};
		LogString configFile;
		spi::ConfigurationStatus status;
		std::tie(status, configFile) = DefaultConfigurator::configureFromFile(paths, names);
		LOGUNIT_ASSERT_EQUAL(status, spi::ConfigurationStatus::Configured);
		LOGUNIT_ASSERT(configFile.npos != configFile.find(LOG4CXX_STR("DOMTestCase5_good.xml")));
		// Prevent "DOMTestCase5_good.xml" use in subsequent default configuration
		DefaultConfigurator::setConfigurationFileName(LogString());
	}

};

LOGUNIT_TEST_SUITE_REGISTRATION(DOMTestCase);

