/*
**++
**
** Copyright (c) 2002  Microsoft Corporation
**
**
** Module Name:
**
**	    writerconfig.h
**
**
** Abstract:
**
**	declare classes that encapsulate the Test writer's configuration
**
** Author:
**
**	Reuven Lax      [reuvenl]       04-June-2002
**
**
**
** Revision History:
**
**--
*/

#ifndef _WRITERCONFIG_H_
#define _WRITERCONFIG_H_

///////////////////////////////////////////////////////////////////////////////
// Includes

#include <string>
#include <algorithm>
#include <vector>
#include "vs_xml.hxx"
#include "utility.h"

using std::wstring;
using Utility::missingAttribute;
using Utility::missingElement;
using Utility::AutoCS;

///////////////////////////////////////////////////////////////////////////////
// Declarations 

// this namespace contains all XML data
namespace XMLData	{
	// names of attributes and elements
	extern wchar_t AlternateLocationMapping[];
	extern wchar_t Component[];
	extern wchar_t ComponentFile[];
	extern wchar_t ExcludeFile[];
	extern wchar_t NewTarget[];
	extern wchar_t FailEvent[];
	extern wchar_t Dependency[];
}

///////////////////////////////////////////////////////////////////////////////
// Class Declarations

// this is the generic collection class for sequences in the XML document
template <class T, wchar_t ElementName[]>
class XMLCollection	{
public:
	// required typedefs for a collection class
	typedef const T value_type ;
	typedef const T& reference ;
	typedef const T& const_reference ;
	typedef T* pointer ;
	typedef const T* const_pointer ;
	typedef long size_type ;

	class Iterator;
	typedef  Iterator iterator;	
	typedef Iterator const_iterator ;

	// The iterator for objects in the collection.  The iterator is read only -- objects in the collection can't be modified
	class Iterator : public std::iterator<std::input_iterator_tag, T>	{		
		CXMLDocument m_doc;
		mutable long* m_identifier;		
		mutable T* m_currentElement;
		bool m_pastEnd;		
		unsigned long m_index;
	public:
		Iterator() :  m_identifier(NULL), m_currentElement(NULL), m_pastEnd(true), m_index(0) 	{}
		Iterator(const Iterator& other) : m_identifier(NULL)	{ *this = other; }
		Iterator(const XMLCollection& collection)  : m_doc(collection.m_doc), m_currentElement(NULL), m_pastEnd(false), m_index(0)
		{ 
			// the assumption is that m_doc is currently at a node with type ElementName
			// bad things will ensue if this is not true			
			m_doc.SetToplevel(); 

			// m_identifer is used to ensure the following statements			
			//		iterator  i1 = ...;
			//		iterator  i2 = i1;
			//		assert(i1 == i2);
			//		assert(++i1 == ++i2);
			m_identifier = new long(1);
			if (m_identifier == NULL)
				throw std::bad_alloc();
		}
		
		virtual ~Iterator()	{ 
			if (m_identifier && --*m_identifier == 0)
				delete m_identifier;
			
			delete m_currentElement; 
		}
		
		Iterator& operator=(const Iterator& other)	{
			if (&other == this)
				return *this;
			
			m_currentElement = NULL; 
			m_doc = other.m_doc; 
			m_pastEnd = other.m_pastEnd; 
			m_index = other.m_index; 

			// make the reference count right
			if (other.m_identifier)
				++*other.m_identifier;
			if(m_identifier && --*m_identifier == 0)
				delete m_identifier;
			
			m_identifier = other.m_identifier;

			return *this;
		}
		
		bool operator==(const Iterator& other) const	 { 
			return (m_pastEnd && other.m_pastEnd) ||
				      ((m_identifier == other.m_identifier) && (m_index == other.m_index) && !m_pastEnd && !other.m_pastEnd); 
		}
		
		bool operator!=(const Iterator& other) const  { return !(*this == other); }

		const_reference  operator*() const	{
			assert(m_identifier);
			assert(!m_pastEnd);
			
			if (!m_currentElement)	
				m_currentElement = new T(m_doc);
			if (m_currentElement == NULL)
				throw std::bad_alloc();				
			
			return *m_currentElement;	
		}
		
		const_pointer operator->() const	{ return &**this; }
		
		Iterator& operator++() 	{
			if (m_pastEnd)	{
				assert(false);
				return *this;
			}

			assert(m_identifier);
			
			delete m_currentElement;
			m_currentElement = NULL;

			if (!m_doc.FindElement(ElementName, false))	
				m_pastEnd = true;

			++m_index;
			
			return *this;
		}
		
		Iterator operator++(int) 	{
			Iterator temp = *this;
			++*this;
			return temp;
		}
	};
	
	XMLCollection() :  m_size(0) {}	// initialize an empty collection
	XMLCollection(const XMLCollection& other)	{ *this = other; }
	XMLCollection(CXMLDocument& document) : m_doc(document), m_size(-1)	{	m_doc.SetToplevel(); }
	virtual ~XMLCollection()	{}
	
	XMLCollection& operator= (const XMLCollection& other)	{ 
		m_doc = other.m_doc; 
		m_size = other.m_size; 

		return *this;
	}

	bool operator==(const XMLCollection& other) const	{ 
		return (size() == other.size()) && std::equal(begin(), end(), other.begin()); 
	}
	bool operator!=(const XMLCollection& other) const	{ return !(*this == other); }
	
	size_type size() const	{
		// if we've already calculated the size, return it
		if (m_size != -1)
			return m_size;

		// otherwise, calculate the size and return it
		assert(!m_doc.IsEmpty());				// if so, then m_size should==0, and we wouldn't be here
		size_type size = 0;
		iterator current(*this);		// can't use begin()/end() as that would recurse
		while (current != m_pastEndIterator)	{
			++size;
			++current;
		}
		assert(size > 0);

		return (m_size = size);
	}		
	
	size_type max_size() const	{ return LONG_MAX; }
	bool empty() const	{ return size() == 0; }
	iterator begin() const	{ return empty() ? m_pastEndIterator : Iterator(*this); 	}
	iterator end() const	{ return m_pastEndIterator; }
private:
	friend class Iterator;
	
	CXMLDocument m_doc;
	mutable long m_size;
	Iterator m_pastEndIterator;
};

// little class to ensure that the document is always reset at the end of each function
struct Resetter	{
	CXMLDocument& m_config;
	Resetter(CXMLDocument& config) : m_config(config)	{}
	~Resetter()	{ m_config.ResetToDocument(); }
};

// generic file specification. 
struct File	{
	File(CXMLDocument node);
	File(const wstring& path, const wstring& filespec, bool recursive) : 
							m_path(path), m_filespec(filespec), m_recursive(recursive)		{
		std::transform(m_path.begin(), m_path.end(), m_path.begin(), towupper);
		std::transform(m_filespec.begin(), m_filespec.end(), m_filespec.begin(), towupper);
	}
	bool operator==(const File& other) const	{
		return (m_path == other.m_path) && 
			    (m_filespec == other.m_filespec) && 
			    (m_recursive == other.m_recursive);
	}
	bool operator!=(const File& other) const	{ return !(*this == other); }

	wstring toString() const;
	
	wstring m_path;
	wstring m_filespec;
	bool m_recursive;
};

// file specification together with an alternate-path target.
struct TargetedFile : public File	{
	TargetedFile(CXMLDocument node);
	TargetedFile(const wstring &path, const wstring& filespec, 
			     bool recursive, const wstring& alternate) : File(path, filespec, recursive),
													m_alternatePath(alternate)	{
		std::transform(m_alternatePath.begin(), m_alternatePath.end(), m_alternatePath.begin(), towupper);
	}
	bool operator==(const TargetedFile& other) const	{
		return (m_alternatePath == other.m_alternatePath) && 
			    (File::operator==(other));
	}
	bool operator!=(const TargetedFile& other) const	{ return !(*this == other); }

	wstring toString() const;
	
	wstring m_alternatePath;
};

// Writer restore method
struct RestoreMethod		{
	RestoreMethod(CXMLDocument node);
	bool operator==(const RestoreMethod& other) const	{
		return (m_method == other.m_method) &&
			    (m_writerRestore == other.m_writerRestore) &&
			    (m_service ==other.m_service) &&
			    (m_rebootRequired == other.m_rebootRequired) &&
			    (m_alternateLocations == other.m_alternateLocations);
	}
	bool operator!=(const RestoreMethod& other) const	{ return !(*this == other); }

	wstring toString() const;
	
	VSS_RESTOREMETHOD_ENUM m_method;
	VSS_WRITERRESTORE_ENUM m_writerRestore;
	wstring m_service;
	bool m_rebootRequired;

	typedef XMLCollection<TargetedFile, XMLData::AlternateLocationMapping> AlternateList ;
	AlternateList m_alternateLocations;	
};


// component dependency
struct Dependency   {
    Dependency(CXMLDocument node);
    bool operator==(const Dependency& other) const  {
        return (m_writerId == other.m_writerId) &&
                    (m_logicalPath == other.m_logicalPath) &&
                    (m_componentName == other.m_componentName);
    }

    bool operator!=(const Dependency& other) const  { return !(*this == other); }

    wstring toString() const;
    
    VSS_ID m_writerId;
    wstring m_logicalPath;
    wstring m_componentName;
};

// Writer component
struct ComponentBase	{	
	ComponentBase(const wstring& path = L"", const wstring& name = L"") : m_logicalPath(path), m_name(name)
		{}
	wstring toString() const;
	
	wstring m_logicalPath;
	wstring m_name;
};

struct  Component : public ComponentBase    {
	Component(CXMLDocument node);

	VSS_COMPONENT_TYPE m_componentType;
	VSS_RESTORE_TARGET m_restoreTarget;
	bool m_selectable;
	bool m_selectableForRestore;

	typedef XMLCollection<TargetedFile, XMLData::ComponentFile> ComponentFileList;
	typedef std::vector<TargetedFile> TargetList;
	typedef XMLCollection<Dependency, XMLData::Dependency> DependencyList;
	
	ComponentFileList m_files;
	DependencyList m_dependencies;
	TargetList m_newTargets;
};

// comparison operators for Component and ComponentBase
bool operator==(const ComponentBase& left, const ComponentBase& right);
bool operator!=(const ComponentBase& left, const ComponentBase& right);
bool operator==(const Component& left, const Component& right);
bool operator!=(const Component& left, const Component& right);

// Writer event.
struct  WriterEvent	{
	WriterEvent(CXMLDocument node);
	WriterEvent(Utility::Events event, bool retryable = true, long failures = 1) : 
				m_writerEvent(event), m_retryable(retryable), 
				m_numFailures(failures)	{}
	bool operator==(const WriterEvent& other) const	{ return m_writerEvent == other.m_writerEvent; }
	bool operator!=(const WriterEvent& other) const	{ return !(*this == other); }
	
	Utility::Events m_writerEvent;
	bool m_retryable;
	long m_numFailures;
};


// Singleton class that encapsulates writer configuration
class WriterConfiguration	{
private:
	// disallow explicit creation of this class
	WriterConfiguration()	{}
	WriterConfiguration(WriterConfiguration&);
	operator= (WriterConfiguration&);

	mutable CComAutoCriticalSection m_section;
	mutable CXMLDocument m_doc;

	template <class T, wchar_t ElementName[]>
	const XMLCollection<T, ElementName> getCollection() const	{
		assert(m_doc.GetLevel() == 0);
		AutoCS critical(m_section);
		Resetter reset(m_doc);

		if (m_doc.FindElement(ElementName, true))
			return XMLCollection<T, ElementName>(m_doc);
		else
			return XMLCollection<T,ElementName>();
	}
public:
	typedef XMLCollection<File, XMLData::ExcludeFile> ExcludeFileList;
	typedef XMLCollection<Component, XMLData::Component>ComponentList;
	typedef XMLCollection<WriterEvent, XMLData::FailEvent> FailEventList;
	static WriterConfiguration* instance();

	void loadFromXML(const wstring& xml);
	VSS_USAGE_TYPE usage() const;
	Utility::Verbosity  verbosity() const;
	bool checkExcludes() const;
	bool checkIncludes() const;
	RestoreMethod  restoreMethod() const;
	const  ExcludeFileList excludeFiles() const
		{ return getCollection<File, XMLData::ExcludeFile>(); }
	const  ComponentList components() const
		{ return getCollection<Component, XMLData::Component>(); }
	const  FailEventList failEvents() const
		{ return getCollection<WriterEvent, XMLData::FailEvent>(); }
};

// return the singleton instance of the class
// This is always called for the first time at the beginning of main, so no critical section
// need be involved
inline WriterConfiguration* WriterConfiguration::instance()
{
	static WriterConfiguration configuration;

	return &configuration;
}

#endif

