/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * 
 * Copyright 2000, 2010 Oracle and/or its affiliates.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/

// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_registry.hxx"

#include "registry/registry.hxx"
#include "registry/reader.hxx"
#include "registry/version.h"
#include "fileurl.hxx"
#include "options.hxx"

#include <rtl/ustring.hxx>
#include <osl/diagnose.h>

#include <stdio.h>
#include <string.h>

#include <set>
#include <vector>
#include <string>

using namespace rtl;
using namespace registry::tools;

typedef std::set< rtl::OUString > StringSet;

class Options_Impl : public Options
{
public:
	explicit Options_Impl(char const * program)
		: Options(program), 
          m_bFullCheck(false),
          m_bForceOutput(false),
          m_bUnoTypeCheck(false),
          m_checkUnpublished(false)
		{}
	
    std::string const & getRegName1() const { return m_regName1; }
    std::string const & getRegName2() const { return m_regName2; }

	bool isStartKeyValid() const { return (m_startKey.getLength() > 0); }
	OUString const & getStartKey() const { return m_startKey; }
    bool matchedWithExcludeKey( const OUString& keyName) const;

	bool fullCheck() const { return m_bFullCheck; }
	bool forceOutput() const { return m_bForceOutput; }
	bool unoTypeCheck() const { return m_bUnoTypeCheck; }
    bool checkUnpublished() const { return m_checkUnpublished; }

protected:
    bool setRegName_Impl(char c, std::string const & param);

	virtual void printUsage_Impl() const;
	virtual bool initOptions_Impl (std::vector< std::string > & rArgs);
	
    std::string m_regName1;	
    std::string m_regName2;	
	OUString    m_startKey;
	StringSet	m_excludeKeys;
	bool m_bFullCheck;
	bool m_bForceOutput;
	bool m_bUnoTypeCheck;
    bool m_checkUnpublished;
};	

#define U2S( s ) OUStringToOString(s, RTL_TEXTENCODING_UTF8).getStr()

inline rtl::OUString makeOUString (std::string const & s)
{
    return rtl::OUString(s.c_str(), s.size(), RTL_TEXTENCODING_UTF8, OSTRING_TO_OUSTRING_CVTFLAGS);
}

inline rtl::OUString shortName(rtl::OUString const & fullName)
{
    return fullName.copy(fullName.lastIndexOf('/') + 1);
}

bool Options_Impl::setRegName_Impl(char c, std::string const & param)
{
    bool one = (c == '1'), two = (c == '2');
    if (one)
        m_regName1 = param;
    if (two)
        m_regName2 = param;
    return (one || two);
}

//virtual
void Options_Impl::printUsage_Impl() const
{
    std::string const & rProgName = getProgramName();
    fprintf(stderr,
            "Usage: %s -r1<filename> -r2<filename> [-options] | @<filename>\n", rProgName.c_str()
            );
    fprintf(stderr,
            "    -r1<filename>  = filename specifies the name of the first registry.\n"
            "    -r2<filename>  = filename specifies the name of the second registry.\n"
            "    @<filename>    = filename specifies a command file.\n"
            "Options:\n"
            "    -s<name>  = name specifies the name of a start key. If no start key\n"
            "     |S<name>   is specified the comparison starts with the root key.\n"
            "    -x<name>  = name specifies the name of a key which won't be compared. All\n"
            "     |X<name>   subkeys won't be compared also. This option can be used more than once.\n"
            "    -f|F      = force the detailed output of any diffenrences. Default\n"
            "                is that only the number of differences is returned.\n"
            "    -c|C      = make a complete check, that means any differences will be\n"
            "                detected. Default is only a compatibility check that means\n"
            "                only UNO typelibrary entries will be checked.\n"
            "    -t|T      = make an UNO type compatiblity check. This means that registry 2\n"
            "                will be checked against registry 1. If a interface in r2 contains\n"
            "                more methods or the methods are in a different order as in r1, r2 is\n"
            "                incompatible to r1. But if a service in r2 supports more properties as\n"
            "                in r1 and the new properties are 'optional' it is compatible.\n"
            "    -u|U      = additionally check types that are unpublished in registry 1.\n"
            "    -h|-?     = print this help message and exit.\n"
            );
    fprintf(stderr,
            "\n%s Version 1.0\n\n", rProgName.c_str()
            );
}	

// virtual
bool Options_Impl::initOptions_Impl (std::vector< std::string > & rArgs)
{
    std::vector< std::string >::const_iterator first = rArgs.begin(), last = rArgs.end();
    for (; first != last; ++first)
	{
		if ((*first)[0] != '-')
		{
            return badOption("invalid", (*first).c_str());
        }
        switch ((*first)[1])
        {
        case 'r':
        case 'R':
            {
                if (!((++first != last) && ((*first)[0] != '-')))
                {
                    return badOption("invalid", (*first).c_str());
                }

                std::string option(*first), param;
                if (option.size() == 1)
                {
                    // "-r<n><space><param>"
                    if (!((++first != last) && ((*first)[0] != '-')))
                    {
                        return badOption("invalid", (*first).c_str());
                    }
                    param = (*first);
                }
                else
                {
                    // "-r<n><param>"
                    param = std::string(&(option[1]), option.size() - 1);
                }
                if (!setRegName_Impl(option[0], param))
                {
                    return badOption("invalid", option.c_str());
                }
                break;
            }
        case 's':
        case 'S':
            {
                if (!((++first != last) && ((*first)[0] != '-')))
                {
                    return badOption("invalid", (*first).c_str());
                }
                m_startKey = makeOUString(*first);
                break;
            }
        case 'x':
        case 'X':
            {
                if (!((++first != last) && ((*first)[0] != '-')))
                {
                    return badOption("invalid", (*first).c_str());
                }
                m_excludeKeys.insert(makeOUString(*first));
                break;
            }
        case 'f':
        case 'F':
            {
			    if ((*first).size() > 2)
                {
                    return badOption("invalid", (*first).c_str());
                }
                m_bForceOutput = sal_True;
                break;
            }
        case 'c':
        case 'C':
            {
			    if ((*first).size() > 2)
                {
                    return badOption("invalid", (*first).c_str());
                }
                m_bFullCheck = sal_True;
                break;
            }
        case 't':
        case 'T':
            {
			    if ((*first).size() > 2)
                {
                    return badOption("invalid", (*first).c_str());
                }
                m_bUnoTypeCheck = sal_True;
                break;
            }
        case 'u':
        case 'U':
            {
			    if ((*first).size() > 2)
                {
                    return badOption("invalid", (*first).c_str());
                }
                m_checkUnpublished = true;
                break;
            }
        case 'h':
        case '?':
            {
			    if ((*first).size() > 2)
                {
                    return badOption("invalid", (*first).c_str());
                }
                return printUsage();
                // break; // Unreachable
            }
        default:
            {
                return badOption("unknown", (*first).c_str());
                // break; // Unreachable
            }
        }
    }

    if ( m_regName1.size() == 0 )
    {
        return badOption("missing", "-r1");
    }
    if ( m_regName2.size() == 0 )
    {
        return badOption("missing", "-r2");
    }
    return true;
}

bool Options_Impl::matchedWithExcludeKey( const OUString& keyName) const
{
    if (!m_excludeKeys.empty())
    {
        StringSet::const_iterator first = m_excludeKeys.begin(), last = m_excludeKeys.end();
        for (; first != last; ++first)
        {
            if (keyName.indexOf(*first) == 0)
                return true;
        }
    }
    return false;
}

static char const * getTypeClass(RTTypeClass typeClass)
{
	switch (typeClass)
	{
		case RT_TYPE_INTERFACE:
			return "INTERFACE";
		case RT_TYPE_MODULE:
			return "MODULE";
		case RT_TYPE_STRUCT:
			return "STRUCT";
		case RT_TYPE_ENUM:
			return "ENUM";
		case RT_TYPE_EXCEPTION:
			return "EXCEPTION";
		case RT_TYPE_TYPEDEF:
			return "TYPEDEF";
		case RT_TYPE_SERVICE:
			return "SERVICE";
		case RT_TYPE_OBJECT:
			return "OBJECT";
		case RT_TYPE_CONSTANTS:
			return "CONSTANTS";
        default:
            return "INVALID";
	}
}	

static OString getFieldAccess(RTFieldAccess fieldAccess)
{
	OString ret;	
	if ( (fieldAccess & RT_ACCESS_INVALID) == RT_ACCESS_INVALID )
	{
		ret += OString("INVALID");
	}
	if ( (fieldAccess & RT_ACCESS_READONLY) == RT_ACCESS_READONLY )
	{
		ret += OString(ret.getLength() > 0 ? ",READONLY" : "READONLY");
	}
	if ( (fieldAccess & RT_ACCESS_OPTIONAL) == RT_ACCESS_OPTIONAL )
	{
		ret += OString(ret.getLength() > 0 ? ",OPTIONAL" : "OPTIONAL");
	}
	if ( (fieldAccess & RT_ACCESS_MAYBEVOID) == RT_ACCESS_MAYBEVOID )
	{
		ret += OString(ret.getLength() > 0 ? ",MAYBEVOID" : "MAYBEVOID");
	}
	if ( (fieldAccess & RT_ACCESS_BOUND) == RT_ACCESS_BOUND )
	{
		ret += OString(ret.getLength() > 0 ? ",BOUND" : "BOUND");
	}
	if ( (fieldAccess & RT_ACCESS_CONSTRAINED) == RT_ACCESS_CONSTRAINED )
	{
		ret += OString(ret.getLength() > 0 ? ",CONSTRAINED" : "CONSTRAINED");
	}
	if ( (fieldAccess & RT_ACCESS_TRANSIENT) == RT_ACCESS_TRANSIENT )
	{
		ret += OString(ret.getLength() > 0 ? ",TRANSIENT" : "TRANSIENT");
	}
	if ( (fieldAccess & RT_ACCESS_MAYBEAMBIGUOUS) == RT_ACCESS_MAYBEAMBIGUOUS )
	{
		ret += OString(ret.getLength() > 0 ? ",MAYBEAMBIGUOUS" : "MAYBEAMBIGUOUS");
	}
	if ( (fieldAccess & RT_ACCESS_MAYBEDEFAULT) == RT_ACCESS_MAYBEDEFAULT )
	{
		ret += OString(ret.getLength() > 0 ? ",MAYBEDEFAULT" : "MAYBEDEFAULT");
	}
	if ( (fieldAccess & RT_ACCESS_REMOVEABLE) == RT_ACCESS_REMOVEABLE )
	{
		ret += OString(ret.getLength() > 0 ? ",REMOVEABLE" : "REMOVEABLE");
	}
	if ( (fieldAccess & RT_ACCESS_ATTRIBUTE) == RT_ACCESS_ATTRIBUTE )
	{
		ret += OString(ret.getLength() > 0 ? ",ATTRIBUTE" : "ATTRIBUTE");
	}
	if ( (fieldAccess & RT_ACCESS_PROPERTY) == RT_ACCESS_PROPERTY )
	{
		ret += OString(ret.getLength() > 0 ? ",PROPERTY" : "PROPERTY");
	}
	if ( (fieldAccess & RT_ACCESS_CONST) == RT_ACCESS_CONST )
	{
		ret += OString(ret.getLength() > 0 ? ",CONST" : "CONST");
	}
	if ( (fieldAccess & RT_ACCESS_READWRITE) == RT_ACCESS_READWRITE )
	{
		ret += OString(ret.getLength() > 0 ? ",READWRITE" : "READWRITE");
	}
	return ret;
} 

static char const * getConstValueType(RTConstValue& constValue)
{
	switch (constValue.m_type)
	{
		case RT_TYPE_BOOL:
			return "sal_Bool";
		case RT_TYPE_BYTE:
			return "sal_uInt8";
		case RT_TYPE_INT16:
			return "sal_Int16";
		case RT_TYPE_UINT16:
			return "sal_uInt16";
		case RT_TYPE_INT32:
			return "sal_Int32";
		case RT_TYPE_UINT32:
			return "sal_uInt32";
//		case RT_TYPE_INT64:
//			return "sal_Int64";
//		case RT_TYPE_UINT64:
//			return "sal_uInt64";
		case RT_TYPE_FLOAT:
			return "float";
		case RT_TYPE_DOUBLE:
			return "double";
		case RT_TYPE_STRING:
			return "sal_Unicode*";
        default:
            return "NONE";
	}	
}	

static void printConstValue(RTConstValue& constValue)
{
	switch (constValue.m_type)
	{
		case RT_TYPE_NONE:
			fprintf(stdout, "none");
            break;
		case RT_TYPE_BOOL:
			fprintf(stdout, "%s", constValue.m_value.aBool ? "TRUE" : "FALSE");
            break;
		case RT_TYPE_BYTE:
			fprintf(stdout, "%d", constValue.m_value.aByte);
            break;
		case RT_TYPE_INT16:
			fprintf(stdout, "%d", constValue.m_value.aShort);
            break;
		case RT_TYPE_UINT16:
			fprintf(stdout, "%d", constValue.m_value.aUShort);
            break;
		case RT_TYPE_INT32:
			fprintf(
                stdout, "%ld",
                sal::static_int_cast< long >(constValue.m_value.aLong));
            break;
		case RT_TYPE_UINT32:
			fprintf(
                stdout, "%lu",
                sal::static_int_cast< unsigned long >(
                    constValue.m_value.aULong));
            break;
//		case RT_TYPE_INT64:
//			fprintf(stdout, "%d", constValue.m_value.aHyper);
//		case RT_TYPE_UINT64:
//			fprintf(stdout, "%d", constValue.m_value.aUHyper);
		case RT_TYPE_FLOAT:
			fprintf(stdout, "%f", constValue.m_value.aFloat);
            break;
		case RT_TYPE_DOUBLE:
			fprintf(stdout, "%f", constValue.m_value.aDouble);
            break;
		case RT_TYPE_STRING:
			fprintf(
                stdout, "%s",
                (rtl::OUStringToOString(
                    constValue.m_value.aString, RTL_TEXTENCODING_UTF8).
                 getStr()));
            break;
        default:
            break;
	}
}

static void dumpTypeClass(sal_Bool & rbDump, RTTypeClass typeClass, OUString const & keyName)
{
    if (rbDump)
        fprintf(stdout, "%s: %s\n", getTypeClass(typeClass), U2S(keyName));
    rbDump = sal_False;
}

static sal_uInt32 checkConstValue(Options_Impl const & options,
                                  const OUString& keyName,
								  RTTypeClass typeClass,
								  sal_Bool & bDump,
								  RTConstValue& constValue1,
								  RTConstValue& constValue2,
								  sal_uInt16 index1)
{
	switch (constValue1.m_type)
	{
        case RT_TYPE_INVALID:
            break;
		case RT_TYPE_BOOL:
			if (constValue1.m_value.aBool != constValue2.m_value.aBool)
			{
				if ( options.forceOutput() && !options.unoTypeCheck() )
				{
                    dumpTypeClass(bDump, typeClass, keyName);
                    fprintf(stdout, "  Field %d: Value1 = %s  !=  Value2 = %s\n", index1,
                            constValue1.m_value.aBool ? "TRUE" : "FALSE",
                            constValue2.m_value.aBool ? "TRUE" : "FALSE");
				}
				return 1;
			}
			break;
		case RT_TYPE_BYTE:
			if (constValue1.m_value.aByte != constValue2.m_value.aByte)
			{
				if ( options.forceOutput() && !options.unoTypeCheck() )
				{
					dumpTypeClass(bDump, typeClass, keyName);
                    fprintf(stdout, "  Field %d: Value1 = %d  !=  Value2 = %d\n", index1,
                            constValue1.m_value.aByte, constValue2.m_value.aByte);
				}
				return 1;
			}
			break;
		case RT_TYPE_INT16:
			if (constValue1.m_value.aShort != constValue2.m_value.aShort)
			{
				if ( options.forceOutput() && !options.unoTypeCheck() )
				{
                    dumpTypeClass(bDump, typeClass, keyName);
                    fprintf(stdout, "  Field %d: Value1 = %d  !=  Value2 = %d\n", index1,
                            constValue1.m_value.aShort, constValue2.m_value.aShort);
				}
				return 1;
			}
			break;
		case RT_TYPE_UINT16:
			if (constValue1.m_value.aUShort != constValue2.m_value.aUShort)
			{
				if ( options.forceOutput() && !options.unoTypeCheck() )
				{
                    dumpTypeClass(bDump, typeClass, keyName);
                    fprintf(stdout, "  Field %d: Value1 = %d  !=  Value2 = %d\n", index1,
                            constValue1.m_value.aUShort, constValue2.m_value.aUShort);
				}
				return 1;
			}
			break;
		case RT_TYPE_INT32:
			if (constValue1.m_value.aLong != constValue2.m_value.aLong)
			{
				if ( options.forceOutput() && !options.unoTypeCheck() )
				{
                    dumpTypeClass(bDump, typeClass, keyName);
                    fprintf(stdout, "  Field %d: Value1 = %ld  !=  Value2 = %ld\n", index1,
                            sal::static_int_cast< long >(constValue1.m_value.aLong),
                            sal::static_int_cast< long >(constValue2.m_value.aLong));
				}
				return 1;
			}
			break;
		case RT_TYPE_UINT32:
			if (constValue1.m_value.aULong != constValue2.m_value.aULong)
			{
				if ( options.forceOutput() && !options.unoTypeCheck() )
				{
                    dumpTypeClass(bDump, typeClass, keyName);
                    fprintf(stdout, "  Field %d: Value1 = %lu  !=  Value2 = %lu\n", index1,
                            sal::static_int_cast< unsigned long >(constValue1.m_value.aULong),
                            sal::static_int_cast< unsigned long >(constValue2.m_value.aULong));
				}
				return 1;
			}
			break;
		case RT_TYPE_INT64:
			if (constValue1.m_value.aHyper != constValue2.m_value.aHyper)
			{
				if ( options.forceOutput() && !options.unoTypeCheck() )
				{
                    dumpTypeClass(bDump, typeClass, keyName);
					fprintf(
                        stdout, "  Field %d: Value1 = %s  !=  Value2 = %s\n",
                        index1,
                        rtl::OUStringToOString(
                            rtl::OUString::valueOf(constValue1.m_value.aHyper),
                            RTL_TEXTENCODING_ASCII_US).getStr(),
                        rtl::OUStringToOString(
                            rtl::OUString::valueOf(constValue2.m_value.aHyper),
                            RTL_TEXTENCODING_ASCII_US).getStr());
				}
				return 1;
			}
			break;
		case RT_TYPE_UINT64:
			if (constValue1.m_value.aUHyper != constValue2.m_value.aUHyper)
			{
				if ( options.forceOutput() && !options.unoTypeCheck() )
				{
                    dumpTypeClass(bDump, typeClass, keyName);
					fprintf(
                        stdout, "  Field %d: Value1 = %s  !=  Value2 = %s\n",
                        index1,
                        rtl::OUStringToOString(
                            rtl::OUString::valueOf(
                                static_cast< sal_Int64 >(
                                    constValue1.m_value.aUHyper)),
                            RTL_TEXTENCODING_ASCII_US).getStr(),
                        rtl::OUStringToOString(
                            rtl::OUString::valueOf(
                                static_cast< sal_Int64 >(
                                    constValue2.m_value.aUHyper)),
                            RTL_TEXTENCODING_ASCII_US).getStr());
                        // printing the unsigned values as signed should be
                        // acceptable...
				}
				return 1;
			}
			break;
		case RT_TYPE_FLOAT:
			if (constValue1.m_value.aFloat != constValue2.m_value.aFloat)
			{
				if ( options.forceOutput() && !options.unoTypeCheck() )
				{
                    dumpTypeClass(bDump, typeClass, keyName);
                    fprintf(stdout, "  Field %d: Value1 = %f  !=  Value2 = %f\n", index1,
                            constValue1.m_value.aFloat, constValue2.m_value.aFloat);
				}
				return 1;
			}
			break;
		case RT_TYPE_DOUBLE:
			if (constValue1.m_value.aDouble != constValue2.m_value.aDouble)
			{
				if ( options.forceOutput() && !options.unoTypeCheck() )
				{
                    dumpTypeClass(bDump, typeClass, keyName);
                    fprintf(stdout, "  Field %d: Value1 = %f  !=  Value2 = %f\n", index1,
                            constValue1.m_value.aDouble, constValue2.m_value.aDouble);
				}
				return 1;
			}
			break;
        default:
            OSL_ASSERT(false);
            break;
	}	
	return 0;
}	

static sal_uInt32 checkField(Options_Impl const & options,
                             const OUString& keyName,
							 RTTypeClass typeClass,
							 sal_Bool & bDump,
							 typereg::Reader& reader1, 
							 typereg::Reader& reader2,
							 sal_uInt16 index1,
                             sal_uInt16 index2)
{
	sal_uInt32 nError = 0;
	if ( reader1.getFieldName(index1) != reader2.getFieldName(index2) )
	{
		if ( options.forceOutput() && !options.unoTypeCheck() )
		{
            dumpTypeClass (bDump, typeClass, keyName);
            fprintf(stdout, "  Field %d: Name1 = %s  !=  Name2 = %s\n", index1,
                    U2S(reader1.getFieldName(index1)), U2S(reader2.getFieldName(index2)));
		}
		nError++;
	}
	if ( reader1.getFieldTypeName(index1) != reader2.getFieldTypeName(index2) )
	{
		if ( options.forceOutput() && !options.unoTypeCheck() )
		{
            dumpTypeClass (bDump, typeClass, keyName);
            fprintf(stdout, "  Field %d: Type1 = %s  !=  Type2 = %s\n", index1,
                    U2S(reader1.getFieldTypeName(index1)), U2S(reader2.getFieldTypeName(index2)));
		}
		nError++;
	}
    else
	{
		RTConstValue constValue1 = reader1.getFieldValue(index1);
		RTConstValue constValue2 = reader2.getFieldValue(index2);
		if ( constValue1.m_type != constValue2.m_type )
		{
			if ( options.forceOutput() && !options.unoTypeCheck() )
			{
                dumpTypeClass (bDump, typeClass, keyName);
                fprintf(stdout, "  Field %d: Access1 = %s  !=  Access2 = %s\n", index1,
                        getConstValueType(constValue1), getConstValueType(constValue2));
                fprintf(stdout, "  Field %d: Value1 = ", index1);
                printConstValue(constValue1);
                fprintf(stdout, "  !=  Value2 = ");
                printConstValue(constValue1);
                fprintf(stdout, "\n;");
			}
			nError++;				
		}
        else
		{
			nError += checkConstValue(options, keyName, typeClass, bDump, constValue1, constValue2, index1);
		}
	}

	if ( reader1.getFieldFlags(index1) != reader2.getFieldFlags(index2) )
	{
		if ( options.forceOutput() && !options.unoTypeCheck() )
		{
            dumpTypeClass (bDump, typeClass, keyName);
            fprintf(stdout, "  Field %d: FieldAccess1 = %s  !=  FieldAccess2 = %s\n", index1,
                    getFieldAccess(reader1.getFieldFlags(index1)).getStr(),
                    getFieldAccess(reader1.getFieldFlags(index2)).getStr());
		}
		nError++;				
	}

	if ( options.fullCheck() && (reader1.getFieldDocumentation(index1) != reader2.getFieldDocumentation(index2)) )
	{
		if ( options.forceOutput() && !options.unoTypeCheck() )
		{
            dumpTypeClass (bDump, typeClass, keyName);
            fprintf(stdout, "  Field %d: Doku1 = %s\n             Doku2 = %s\n", index1,
                    U2S(reader1.getFieldDocumentation(index1)), U2S(reader2.getFieldDocumentation(index2)));
		}
		nError++;
	}
	return nError;
}

static char const * getMethodMode(RTMethodMode methodMode)
{
	switch ( methodMode )
	{
		case RT_MODE_ONEWAY:
			return "ONEWAY";
		case RT_MODE_ONEWAY_CONST:
			return "ONEWAY,CONST";
		case RT_MODE_TWOWAY:
			return "NONE";
		case RT_MODE_TWOWAY_CONST:
			return "CONST";
        default:
        	return "INVALID";
	}
} 

static char const * getParamMode(RTParamMode paramMode)
{
	switch ( paramMode )
	{
		case RT_PARAM_IN:
			return "IN";
		case RT_PARAM_OUT:
			return "OUT";
		case RT_PARAM_INOUT:
			return "INOUT";
        default:
        	return "INVALID";
	}
} 

static sal_uInt32 checkMethod(Options_Impl const & options,
                              const OUString& keyName,
							  RTTypeClass typeClass,
							  sal_Bool & bDump,
							  typereg::Reader& reader1, 
							  typereg::Reader& reader2,
							  sal_uInt16 index)
{
	sal_uInt32 nError = 0;
	if ( reader1.getMethodName(index) != reader2.getMethodName(index) )
	{
		if ( options.forceOutput() )
		{
            dumpTypeClass (bDump, typeClass, keyName);
			fprintf(stdout, "  Method1 %d: Name1 = %s  !=  Name2 = %s\n", index,
					U2S(reader1.getMethodName(index)),
					U2S(reader2.getMethodName(index)));
		}
		nError++;
	}

	if ( reader1.getMethodReturnTypeName(index) != reader2.getMethodReturnTypeName(index) )
	{
		if ( options.forceOutput() )
		{
            dumpTypeClass (bDump, typeClass, keyName);
			fprintf(stdout, "  Method1 %d: ReturnType1 = %s  !=  ReturnType2 = %s\n", index,
					U2S(reader1.getMethodReturnTypeName(index)),
					U2S(reader2.getMethodReturnTypeName(index)));
		}
		nError++;
	}

	sal_uInt16 nParams1 = (sal_uInt16)reader1.getMethodParameterCount(index);
	sal_uInt16 nParams2 = (sal_uInt16)reader2.getMethodParameterCount(index);	
	if ( nParams1 != nParams2 )
	{
		if ( options.forceOutput() )
		{
            dumpTypeClass (bDump, typeClass, keyName);
			fprintf(stdout, "  Method %d : nParameters1 = %d  !=  nParameters2 = %d\n", index, nParams1, nParams2);
		}
		nError++;
	}
	sal_uInt16 i=0;
	for (i=0; i < nParams1 && i < nParams2; i++)
	{
		if ( reader1.getMethodParameterTypeName(index, i) != reader2.getMethodParameterTypeName(index, i) )
		{
			if ( options.forceOutput() )
			{
                dumpTypeClass (bDump, typeClass, keyName);
				fprintf(stdout, "  Method %d, Parameter %d: Type1 = %s  !=  Type2 = %s\n", index, i,
						U2S(reader1.getMethodParameterTypeName(index, i)),
						U2S(reader2.getMethodParameterTypeName(index, i)));
			}
			nError++;
		}
		if ( options.fullCheck() && (reader1.getMethodParameterName(index, i) != reader2.getMethodParameterName(index, i)) )
		{
			if ( options.forceOutput() )
			{
                dumpTypeClass (bDump, typeClass, keyName);
				fprintf(stdout, "  Method %d, Parameter %d: Name1 = %s  !=  Name2 = %s\n", index, i,
						U2S(reader1.getMethodParameterName(index, i)),
						U2S(reader2.getMethodParameterName(index, i)));
			}
			nError++;
		}
		if ( reader1.getMethodParameterFlags(index, i) != reader2.getMethodParameterFlags(index, i) )
		{
			if ( options.forceOutput() )
			{
                dumpTypeClass (bDump, typeClass, keyName);
				fprintf(stdout, "  Method %d, Parameter %d: Mode1 = %s  !=  Mode2 = %s\n", index, i,
						getParamMode(reader1.getMethodParameterFlags(index, i)),
						getParamMode(reader2.getMethodParameterFlags(index, i)));
			}
			nError++;
		}
	}
	if ( i < nParams1 && options.forceOutput() )
	{
        dumpTypeClass (bDump, typeClass, keyName);
		fprintf(stdout, "  Registry1: Method %d contains %d more parameters\n", index, nParams1 - i);
	}
	if ( i < nParams2 && options.forceOutput() )
	{
        dumpTypeClass (bDump, typeClass, keyName);
		fprintf(stdout, "  Registry2: Method %d contains %d more parameters\n", index, nParams2 - i);
	}

	sal_uInt16 nExcep1 = (sal_uInt16)reader1.getMethodExceptionCount(index);
	sal_uInt16 nExcep2 = (sal_uInt16)reader2.getMethodExceptionCount(index);	
	if ( nExcep1 != nExcep2 )
	{
		if ( options.forceOutput() )
		{
            dumpTypeClass (bDump, typeClass, keyName);
			fprintf(stdout, "  nExceptions1 = %d  !=  nExceptions2 = %d\n", nExcep1, nExcep2);
		}
		nError++;
	}
	for (i=0; i < nExcep1 && i < nExcep2; i++)
	{
		if ( reader1.getMethodExceptionTypeName(index, i) != reader2.getMethodExceptionTypeName(index, i) )
		{
			if ( options.forceOutput() )
			{
                dumpTypeClass (bDump, typeClass, keyName);
				fprintf(stdout, "  Method %d, Exception %d: Name1 = %s  !=  Name2 = %s\n", index, i,
						U2S(reader1.getMethodExceptionTypeName(index, i)),
						U2S(reader2.getMethodExceptionTypeName(index, i)));
			}
			nError++;
		}
	}
	if ( i < nExcep1 && options.forceOutput() )
	{
        dumpTypeClass (bDump, typeClass, keyName);
		fprintf(stdout, "  Registry1: Method %d contains %d more exceptions\n", index, nExcep1 - i);
	}
	if ( i < nExcep2 && options.forceOutput() )
	{
        dumpTypeClass (bDump, typeClass, keyName);
		fprintf(stdout, "  Registry2: Method %d contains %d more exceptions\n", index, nExcep2 - i);
	}

	if ( reader1.getMethodFlags(index) != reader2.getMethodFlags(index) )
	{
		if ( options.forceOutput() )
		{
            dumpTypeClass (bDump, typeClass, keyName);
			fprintf(stdout, "  Method %d: Mode1 = %s  !=  Mode2 = %s\n", index,
					getMethodMode(reader1.getMethodFlags(index)),
					getMethodMode(reader2.getMethodFlags(index)));
		}
		nError++;
	}

	if ( options.fullCheck() && (reader1.getMethodDocumentation(index) != reader2.getMethodDocumentation(index)) )
	{
		if ( options.forceOutput() )
		{
            dumpTypeClass (bDump, typeClass, keyName);
			fprintf(stdout, "  Method %d: Doku1 = %s\n              Doku2 = %s\n", index,
					U2S(reader1.getMethodDocumentation(index)),
					U2S(reader2.getMethodDocumentation(index)));
		}
		nError++;
	}
	return nError;
}

static char const * getReferenceType(RTReferenceType refType)
{
	switch (refType)
	{
		case RT_REF_SUPPORTS:
			return "RT_REF_SUPPORTS";
		case RT_REF_OBSERVES:
			return "RT_REF_OBSERVES";
		case RT_REF_EXPORTS:
			return "RT_REF_EXPORTS";
		case RT_REF_NEEDS:
			return "RT_REF_NEEDS";
        default:
        	return "RT_REF_INVALID";
	}
}

static sal_uInt32 checkReference(Options_Impl const & options,
                                 const OUString& keyName,
								 RTTypeClass typeClass,
								 sal_Bool & bDump,
								 typereg::Reader& reader1,
							   	 typereg::Reader& reader2,
							   	 sal_uInt16 index1,
								 sal_uInt16 index2)
{
	sal_uInt32 nError = 0;
	if ( reader1.getReferenceTypeName(index1) != reader2.getReferenceTypeName(index2) )
	{
		if ( options.forceOutput() && !options.unoTypeCheck() )
		{
            dumpTypeClass (bDump, typeClass, keyName);
			fprintf(stdout, "  Reference %d: Name1 = %s  !=  Name2 = %s\n", index1,
					U2S(reader1.getReferenceTypeName(index1)),
					U2S(reader2.getReferenceTypeName(index2)));
		}
		nError++;
	}
	if ( reader1.getReferenceTypeName(index1) != reader2.getReferenceTypeName(index2) )
	{
		if ( options.forceOutput() && !options.unoTypeCheck() )
		{
            dumpTypeClass (bDump, typeClass, keyName);
			fprintf(stdout, "  Reference %d: Type1 = %s  !=  Type2 = %s\n", index1,
					getReferenceType(reader1.getReferenceSort(index1)),
					getReferenceType(reader2.getReferenceSort(index2)));
		}
		nError++;
	}
	if ( options.fullCheck() && (reader1.getReferenceDocumentation(index1) != reader2.getReferenceDocumentation(index2)) )
	{
		if ( options.forceOutput() && !options.unoTypeCheck() )
		{
            dumpTypeClass (bDump, typeClass, keyName);
			fprintf(stdout, "  Reference %d: Doku1 = %s\n                 Doku2 = %s\n", index1,
					U2S(reader1.getReferenceDocumentation(index1)),
					U2S(reader2.getReferenceDocumentation(index2)));
		}
		nError++;
	}
	if ( reader1.getReferenceFlags(index1) != reader2.getReferenceFlags(index2) )
	{
		if ( options.forceOutput() && !options.unoTypeCheck() )
		{
            dumpTypeClass (bDump, typeClass, keyName);
			fprintf(stdout, "  Reference %d: Access1 = %s  !=  Access2 = %s\n", index1,
					getFieldAccess(reader1.getReferenceFlags(index1)).getStr(),
					getFieldAccess(reader1.getReferenceFlags(index2)).getStr());
		}
		nError++;
	}
	return nError;
}

static sal_uInt32 checkFieldsWithoutOrder(Options_Impl const & options,
                                          const OUString& keyName,
                                          RTTypeClass typeClass,
                                          sal_Bool & bDump,
                                          typereg::Reader& reader1,
                                          typereg::Reader& reader2)
{
	sal_uInt32 nError = 0;

	sal_uInt16 nFields1 = (sal_uInt16)reader1.getFieldCount();
	sal_uInt16 nFields2 = (sal_uInt16)reader2.getFieldCount();	
    sal_uInt16 i=0, j=0;

    if ( nFields1 > nFields2 )
    {
		if ( options.forceOutput() )
		{
            dumpTypeClass (bDump, typeClass, keyName);
            fprintf(stdout, "  %s1 contains %d more properties as %s2\n",
                    getTypeClass(typeClass), nFields1-nFields2, getTypeClass(typeClass));
        }
    }

    sal_Bool bFound = sal_False;
    ::std::set< sal_uInt16 > moreProps;

    for (i=0; i < nFields1; i++)
    {
        for (j=0; j < nFields2; j++)
        {
            if (!checkField(options, keyName, typeClass, bDump, reader1, reader2, i, j))
            {
                bFound =  sal_True;
                moreProps.insert(j);
                break;
            }
        }
        if (!bFound)
        {
            if (options.forceOutput())
            {
                dumpTypeClass (bDump, typeClass, keyName);
                fprintf(stdout, "  incompatible change: Field %d ('%s') of r1 is not longer a property of this %s in r2\n",
                        i, U2S(shortName(reader1.getFieldName(i))), getTypeClass(typeClass));
            }
            nError++;
        }
        else
		{
			bFound = sal_False;
		}
    }

    if ( typeClass == RT_TYPE_SERVICE && !moreProps.empty() )
    {
        for (j=0; j < nFields2; j++)
        {
            if ( moreProps.find(j) == moreProps.end() )
            {
                if ( (reader2.getFieldFlags(j) & RT_ACCESS_OPTIONAL) != RT_ACCESS_OPTIONAL )
                {
                    if ( options.forceOutput() )
                    {
                        dumpTypeClass (bDump, typeClass, keyName);
                        fprintf(stdout,
                                "  incompatible change: Field %d ('%s') of r2 is a new property"
                                " compared to this %s in r1 and is not 'optional'\n",
                                j, U2S(shortName(reader2.getFieldName(j))), getTypeClass(typeClass));
                    }
                    nError++;
                }
            }
        }
    }
    
    return nError;
}

static sal_uInt32 checkBlob(
    Options_Impl const & options,
    const OUString& keyName,
    typereg::Reader& reader1, sal_uInt32 size1, 
    typereg::Reader& reader2, sal_uInt32 size2)
{
	sal_uInt32 nError = 0;
	sal_Bool bDump = sal_True;

	if ( options.fullCheck() && (size1 != size2) )
	{
		if ( options.forceOutput() )
		{
			fprintf(
                stdout, "    Size1 = %lu    Size2 = %lu\n",
                sal::static_int_cast< unsigned long >(size1),
                sal::static_int_cast< unsigned long >(size2));
		}
	}
    if (reader1.isPublished())
    {
        if (!reader2.isPublished())
        {
            if (options.forceOutput())
            {
                dumpTypeClass(bDump, /*"?"*/ reader1.getTypeClass(), keyName);
                fprintf(stdout, "    published in 1 but unpublished in 2\n");
            }
            ++nError;
        }
    }
    else if (!options.checkUnpublished())
    {
        return nError;
    }
	if ( reader1.getTypeClass() != reader2.getTypeClass() )
	{
		if ( options.forceOutput() )
		{
            dumpTypeClass(bDump, /*"?"*/ reader1.getTypeClass(), keyName);
			fprintf(stdout, "    TypeClass1 = %s  !=  TypeClass2 = %s\n",
					getTypeClass(reader1.getTypeClass()),
					getTypeClass(reader2.getTypeClass()));
		}
		return ++nError;
	}	

	RTTypeClass typeClass = reader1.getTypeClass();
	if ( reader1.getTypeName() != reader2.getTypeName() )
	{
		if ( options.forceOutput() )
		{
            dumpTypeClass(bDump, typeClass, keyName);
			fprintf(stdout, "    TypeName1 = %s  !=  TypeName2 = %s\n",
					U2S(reader1.getTypeName()), U2S(reader2.getTypeName()));
		}
		nError++;				
	}	
	if ( (typeClass == RT_TYPE_INTERFACE ||
		  typeClass == RT_TYPE_STRUCT ||
		  typeClass == RT_TYPE_EXCEPTION) )
	{
        if (reader1.getSuperTypeCount() != reader2.getSuperTypeCount())
        {
            dumpTypeClass(bDump, typeClass, keyName);
            fprintf(
                stdout, "    SuperTypeCount1 = %d  !=  SuperTypeCount2 = %d\n",
                static_cast< int >(reader1.getSuperTypeCount()),
                static_cast< int >(reader2.getSuperTypeCount()));
            ++nError;
        } else
        {
            for (sal_Int16 i = 0; i < reader1.getSuperTypeCount(); ++i)
            {
                if (reader1.getSuperTypeName(i) != reader2.getSuperTypeName(i))
                {
                    if ( options.forceOutput() )
                    {
                        dumpTypeClass(bDump, typeClass, keyName);
                        fprintf(stdout, "    SuperTypeName1 = %s  !=  SuperTypeName2 = %s\n",
                                U2S(reader1.getSuperTypeName(i)), U2S(reader2.getSuperTypeName(i)));
                    }
                    nError++;
                }
            }
        }
	}	

	sal_uInt16 nFields1 = (sal_uInt16)reader1.getFieldCount();
	sal_uInt16 nFields2 = (sal_uInt16)reader2.getFieldCount();	
	sal_Bool bCheckNormal = sal_True;

	if ( (typeClass == RT_TYPE_SERVICE ||
          typeClass == RT_TYPE_MODULE ||
          typeClass == RT_TYPE_CONSTANTS) && options.unoTypeCheck() )
	{
		bCheckNormal = sal_False;
	}

	if ( bCheckNormal )
	{
        if ( nFields1 != nFields2 )
        {
            if ( options.forceOutput() )
            {
                dumpTypeClass(bDump, typeClass, keyName);
                fprintf(stdout, "    nFields1 = %d  !=  nFields2 = %d\n", nFields1, nFields2);
            }
            nError++;
        }

        sal_uInt16 i;
        for (i=0; i < nFields1 && i < nFields2; i++)
        {
            nError += checkField(options, keyName, typeClass, bDump, reader1, reader2, i, i);
        }
        if ( i < nFields1 && options.forceOutput() )
        {
            dumpTypeClass(bDump, typeClass, keyName);
            fprintf(stdout, "    Registry1 contains %d more fields\n", nFields1 - i);
        }
        if ( i < nFields2 && options.forceOutput() )
        {
            dumpTypeClass(bDump, typeClass, keyName);
            fprintf(stdout, "    Registry2 contains %d more fields\n", nFields2 - i);
        }
    }
    else
    {
        nError += checkFieldsWithoutOrder(options, keyName, typeClass, bDump, reader1, reader2);
    }
        
	if ( typeClass == RT_TYPE_INTERFACE )
	{
		sal_uInt16 nMethods1 = (sal_uInt16)reader1.getMethodCount();
		sal_uInt16 nMethods2 = (sal_uInt16)reader2.getMethodCount();	
		if ( nMethods1 != nMethods2 )
		{
			if ( options.forceOutput() )
			{
                dumpTypeClass(bDump, typeClass, keyName);
				fprintf(stdout, "    nMethods1 = %d  !=  nMethods2 = %d\n", nMethods1, nMethods2);
			}
			nError++;
		}

        sal_uInt16 i;
		for (i=0; i < nMethods1 && i < nMethods2; i++)
		{
			nError += checkMethod(options, keyName, typeClass, bDump, reader1, reader2, i);
		}
		if ( i < nMethods1 && options.forceOutput() )
		{
			fprintf(stdout, "    Registry1 contains %d more methods\n", nMethods1 - i);
		}
		if ( i < nMethods2 && options.forceOutput() )
		{
			fprintf(stdout, "    Registry2 contains %d more methods\n", nMethods2 - i);
		}
	}
	if ( typeClass == RT_TYPE_SERVICE )
	{
        sal_uInt16 nReference1 = (sal_uInt16)reader1.getReferenceCount();
		sal_uInt16 nReference2 = (sal_uInt16)reader2.getReferenceCount();	

		if ( !bCheckNormal )
		{
			sal_uInt16 i=0, j=0;

			if ( nReference1 > nReference2 )
			{
				if ( options.forceOutput() )
				{
                    dumpTypeClass(bDump, typeClass, keyName);
					fprintf(stdout, "    service1 contains %d more references as service2\n",
							nReference1-nReference2);
				}
			}

			sal_Bool bFound = sal_False;
            ::std::set< sal_uInt16 > moreReferences;
            
			for (i=0; i < nReference1; i++)
			{
				for (j=0; j < nReference2; j++)
				{
					if (!checkReference(options, keyName, typeClass, bDump, reader1, reader2, i, j))
					{
						bFound =  sal_True;
                        moreReferences.insert(j);
						break;
					}
				}
				if (!bFound)
				{
					if (options.forceOutput())
					{
                        dumpTypeClass(bDump, typeClass, keyName);
						fprintf(stdout,
                                "  incompatible change: Reference %d ('%s') in 'r1' is not longer a reference"
                                " of this service in 'r2'\n", 
							    i, U2S(shortName(reader1.getReferenceTypeName(i))));
					}
					nError++;
				}
                else
				{
					bFound = sal_False;
				}
			}
            
            if ( !moreReferences.empty() )
            {
                for (j=0; j < nReference2; j++)
                {
                    if ( moreReferences.find(j) == moreReferences.end() )
                    {
                        if ( (reader2.getReferenceFlags(j) & RT_ACCESS_OPTIONAL) != RT_ACCESS_OPTIONAL )
                        {
                            if ( options.forceOutput() )
                            {
                                dumpTypeClass(bDump, typeClass, keyName);
                                fprintf(stdout,
                                        "  incompatible change: Reference %d ('%s') of r2 is a new reference"
                                        " compared to this service in r1 and is not 'optional'\n",
									    j, U2S(shortName(reader2.getReferenceTypeName(j))));
                            }
                            nError++;
                        }
                    }
                }
            }
		}
        else
		{
			if ( nReference1 != nReference2 )
			{
				if ( options.forceOutput() )
				{
                    dumpTypeClass(bDump, typeClass, keyName);
					fprintf(stdout, "    nReferences1 = %d  !=  nReferences2 = %d\n", nReference1, nReference2);
				}
				nError++;
			}

            sal_uInt16 i;
			for (i=0; i < nReference1 && i < nReference2; i++)
			{
				nError += checkReference(options, keyName, typeClass, bDump, reader1, reader2, i, i);
			}
			if ( i < nReference1 && options.forceOutput() )
			{
				fprintf(stdout, "    Registry1 contains %d more references\n", nReference1 - i);
			}
			if ( i < nReference2 && options.forceOutput() )
			{
				fprintf(stdout, "    Registry2 contains %d more references\n", nReference2 - i);
			}
		}
	}

	if ( options.fullCheck() && (reader1.getDocumentation() != reader2.getDocumentation()) )
	{
		if ( options.forceOutput() )
		{
            dumpTypeClass(bDump, typeClass, keyName);
			fprintf(stdout, "    Doku1 = %s\n    Doku2 = %s\n",
					U2S(reader1.getDocumentation()), U2S(reader2.getDocumentation()));
		}
		nError++;
	}	
	return nError;
}	

static sal_uInt32 checkValueDifference(
    Options_Impl const & options,
    RegistryKey& key1, RegValueType valueType1, sal_uInt32 size1,
    RegistryKey& key2, RegValueType valueType2, sal_uInt32 size2)
{
	OUString tmpName;
	sal_uInt32 nError = 0;

	if ( valueType1 == valueType2 )
	{
		sal_Bool bEqual = sal_True;
        switch (valueType1)
        {
        case RG_VALUETYPE_LONGLIST:
            {
				RegistryValueList<sal_Int32> valueList1;
				RegistryValueList<sal_Int32> valueList2;
				key1.getLongListValue(tmpName, valueList1);
				key2.getLongListValue(tmpName, valueList2);
				sal_uInt32 length1 = valueList1.getLength();
				sal_uInt32 length2 = valueList1.getLength();
				if ( length1 != length2 )
				{
					bEqual = sal_False;
					break;
				}
				for (sal_uInt32 i=0; i<length1; i++)
				{
					if ( valueList1.getElement(i) != valueList2.getElement(i) )
					{
						bEqual = sal_False;
						break;
					}
				}
            }
            break;
        case RG_VALUETYPE_STRINGLIST:
            {
				RegistryValueList<sal_Char*> valueList1;
				RegistryValueList<sal_Char*> valueList2;
				key1.getStringListValue(tmpName, valueList1);
				key2.getStringListValue(tmpName, valueList2);
				sal_uInt32 length1 = valueList1.getLength();
				sal_uInt32 length2 = valueList1.getLength();
				if ( length1 != length2 )
				{
					bEqual = sal_False;
					break;
				}
				for (sal_uInt32 i=0; i<length1; i++)
				{
					if ( strcmp(valueList1.getElement(i), valueList2.getElement(i)) != 0 )
					{
						bEqual = sal_False;
						break;
					}
				}
            }
            break;
        case RG_VALUETYPE_UNICODELIST:
            {
				RegistryValueList<sal_Unicode*> valueList1;
				RegistryValueList<sal_Unicode*> valueList2;
				key1.getUnicodeListValue(tmpName, valueList1);
				key2.getUnicodeListValue(tmpName, valueList2);
				sal_uInt32 length1 = valueList1.getLength();
				sal_uInt32 length2 = valueList1.getLength();
				if ( length1 != length2 )
				{
					bEqual = sal_False;
					break;
				}
				for (sal_uInt32 i=0; i<length1; i++)
				{
					if ( rtl_ustr_compare(valueList1.getElement(i), valueList2.getElement(i)) != 0 )
					{
						bEqual = sal_False;
						break;
					}
				}
            }
            break;
        default:
            break;
        }
		
		if ( bEqual)
		{
            std::vector< sal_uInt8 > value1(size1);
			key1.getValue(tmpName, &value1[0]);

            std::vector< sal_uInt8 > value2(size2);
			key2.getValue(tmpName, &value2[0]);
		
			bEqual = (rtl_compareMemory(&value1[0], &value2[0], value1.size()) == 0 );
			if ( !bEqual && valueType1 == RG_VALUETYPE_BINARY && valueType2 == RG_VALUETYPE_BINARY )
			{
                typereg::Reader reader1(&value1[0], value1.size(), false, TYPEREG_VERSION_1);
                typereg::Reader reader2(&value2[0], value2.size(), false, TYPEREG_VERSION_1);
				if ( reader1.isValid() && reader2.isValid() )
				{
					return checkBlob(options, key1.getName(), reader1, size1, reader2, size2);
				}
			}
			if ( bEqual )
			{
				return 0;
			}
            else
			{
				if ( options.forceOutput() )
				{
					fprintf(stdout, "Difference: key values of key \"%s\" are different\n", U2S(key1.getName()));
				}
				nError++;
			}
		}
	}
	
	if ( options.forceOutput() )
	{
		switch (valueType1)
		{
		case RG_VALUETYPE_NOT_DEFINED:
			fprintf(stdout, "    Registry 1: key has no value\n");
			break;
		case RG_VALUETYPE_LONG:
            {
                std::vector< sal_uInt8 > value1(size1);
                key1.getValue(tmpName, &value1[0]);

                fprintf(stdout, "    Registry 1: Value: Type = RG_VALUETYPE_LONG\n");
                fprintf(
                    stdout, "                       Size = %lu\n",
                    sal::static_int_cast< unsigned long >(size1));
                fprintf(stdout, "                       Data = %p\n", &value1[0]);
            }
            break;
		case RG_VALUETYPE_STRING:
            {
                std::vector< sal_uInt8 > value1(size1);
                key1.getValue(tmpName, &value1[0]);

                fprintf(stdout, "    Registry 1: Value: Type = RG_VALUETYPE_STRING\n");
                fprintf(
                    stdout, "                       Size = %lu\n",
                    sal::static_int_cast< unsigned long >(size1));
                fprintf(stdout, "                       Data = \"%s\"\n", reinterpret_cast<char const*>(&value1[0]));
            }
            break;
		case RG_VALUETYPE_UNICODE:
            {
                std::vector< sal_uInt8 > value1(size1);
                key1.getValue(tmpName, &value1[0]);

                OUString uStrValue(reinterpret_cast<sal_Unicode const*>(&value1[0]));
                fprintf(stdout, "    Registry 1: Value: Type = RG_VALUETYPE_UNICODE\n");
                fprintf(
                    stdout, "                       Size = %lu\n",
                    sal::static_int_cast< unsigned long >(size1));
                fprintf(stdout, "                       Data = \"%s\"\n", U2S(uStrValue));
            }
            break;
		case RG_VALUETYPE_BINARY:
			fprintf(stdout, "    Registry 1: Value: Type = RG_VALUETYPE_BINARY\n");
			break;
		case RG_VALUETYPE_LONGLIST:
			{
                RegistryValueList<sal_Int32> valueList;
                key1.getLongListValue(tmpName, valueList);
                fprintf(stdout, "    Registry 1: Value: Type = RG_VALUETYPE_LONGLIST\n");
                fprintf(
                    stdout, "                       Size = %lu\n",
                    sal::static_int_cast< unsigned long >(size1));
                sal_uInt32 length = valueList.getLength();
                for (sal_uInt32 i=0; i<length; i++)
                {
                    fprintf(
                        stdout, "                       Data[%lu] = %ld\n",
                        sal::static_int_cast< unsigned long >(i),
                        sal::static_int_cast< long >(valueList.getElement(i)));
                }
			}
			break;
		case RG_VALUETYPE_STRINGLIST:
			{
                RegistryValueList<sal_Char*> valueList;
                key1.getStringListValue(tmpName, valueList);
                fprintf(stdout, "    Registry 1: Value: Type = RG_VALUETYPE_STRINGLIST\n");
                fprintf(
                    stdout, "                       Size = %lu\n",
                    sal::static_int_cast< unsigned long >(size1));
                sal_uInt32 length = valueList.getLength();
                for (sal_uInt32 i=0; i<length; i++)
                {
                    fprintf(
                        stdout, "                       Data[%lu] = \"%s\"\n",
                        sal::static_int_cast< unsigned long >(i),
                        valueList.getElement(i));		
                }
			}
			break;
		case RG_VALUETYPE_UNICODELIST:
			{
                RegistryValueList<sal_Unicode*> valueList;
                key1.getUnicodeListValue(tmpName, valueList);
                fprintf(stdout, "    Registry 1: Value: Type = RG_VALUETYPE_UNICODELIST\n");
                fprintf(
                    stdout, "                       Size = %lu\n",
                    sal::static_int_cast< unsigned long >(size1));
                sal_uInt32 length = valueList.getLength();
                OUString uStrValue;
                for (sal_uInt32 i=0; i<length; i++)
                {
                    uStrValue = OUString(valueList.getElement(i));
                    fprintf(
                        stdout, "                       Data[%lu] = \"%s\"\n",
                        sal::static_int_cast< unsigned long >(i), U2S(uStrValue));
                }
			}
			break;
		}

		switch (valueType2)
		{
		case RG_VALUETYPE_NOT_DEFINED:
			fprintf(stdout, "    Registry 2: key has no value\n");
			break;
		case RG_VALUETYPE_LONG:
            {
                std::vector< sal_uInt8 > value2(size2);
                key2.getValue(tmpName, &value2[0]);

				fprintf(stdout, "    Registry 2: Value: Type = RG_VALUETYPE_LONG\n");
				fprintf(
                    stdout, "                       Size = %lu\n",
                    sal::static_int_cast< unsigned long >(size2));
				fprintf(stdout, "                       Data = %p\n", &value2[0]);
            }
            break;
		case RG_VALUETYPE_STRING:
            {
                std::vector< sal_uInt8 > value2(size2);
                key2.getValue(tmpName, &value2[0]);

				fprintf(stdout, "    Registry 2: Value: Type = RG_VALUETYPE_STRING\n");
				fprintf(
                    stdout, "                       Size = %lu\n",
                    sal::static_int_cast< unsigned long >(size2));
				fprintf(stdout, "                       Data = \"%s\"\n", reinterpret_cast<char const*>(&value2[0]));
            }
            break;
		case RG_VALUETYPE_UNICODE:
            {
                std::vector< sal_uInt8 > value2(size2);
                key2.getValue(tmpName, &value2[0]);
			
				OUString uStrValue(reinterpret_cast<sal_Unicode const*>(&value2[0]));
				fprintf(stdout, "    Registry 2: Value: Type = RG_VALUETYPE_UNICODE\n");
				fprintf(
                    stdout, "                       Size = %lu\n",
                    sal::static_int_cast< unsigned long >(size2));
				fprintf(stdout, "                       Data = \"%s\"\n", U2S(uStrValue));
            }
            break;
		case RG_VALUETYPE_BINARY:
			fprintf(stdout, "    Registry 2: Value: Type = RG_VALUETYPE_BINARY\n");
			break;
		case RG_VALUETYPE_LONGLIST:
			{
                RegistryValueList<sal_Int32> valueList;
                key2.getLongListValue(tmpName, valueList);
                fprintf(stdout, "    Registry 2: Value: Type = RG_VALUETYPE_LONGLIST\n");
                fprintf(
                    stdout, "                       Size = %lu\n",
                    sal::static_int_cast< unsigned long >(size2));
                sal_uInt32 length = valueList.getLength();
                for (sal_uInt32 i=0; i<length; i++)
                {
                    fprintf(
                        stdout, "                       Data[%lu] = %ld\n",
                        sal::static_int_cast< unsigned long >(i),
                        sal::static_int_cast< long >(valueList.getElement(i)));
                }
			}
			break;
		case RG_VALUETYPE_STRINGLIST:
			{
                RegistryValueList<sal_Char*> valueList;
                key2.getStringListValue(tmpName, valueList);
                fprintf(stdout, "    Registry 2: Value: Type = RG_VALUETYPE_STRINGLIST\n");
                fprintf(
                    stdout, "                       Size = %lu\n",
                    sal::static_int_cast< unsigned long >(size2));
                sal_uInt32 length = valueList.getLength();
                for (sal_uInt32 i=0; i<length; i++)
                {
                    fprintf(
                        stdout, "                       Data[%lu] = \"%s\"\n",
                        sal::static_int_cast< unsigned long >(i),
                        valueList.getElement(i));		
                }
			}
			break;
		case RG_VALUETYPE_UNICODELIST:
			{
                RegistryValueList<sal_Unicode*> valueList;
                key2.getUnicodeListValue(tmpName, valueList);
                fprintf(stdout, "    Registry 2: Value: Type = RG_VALUETYPE_UNICODELIST\n");
                fprintf(
                    stdout, "                       Size = %lu\n",
                    sal::static_int_cast< unsigned long >(size2));
                sal_uInt32 length = valueList.getLength();
                OUString uStrValue;
                for (sal_uInt32 i=0; i<length; i++)
                {
                    uStrValue = OUString(valueList.getElement(i));
                    fprintf(
                        stdout, "                       Data[%lu] = \"%s\"\n",
                        sal::static_int_cast< unsigned long >(i), U2S(uStrValue));
                }
			}
			break;
		}
	}	
	return nError;	
}	

static bool hasPublishedChildren(Options_Impl const & options, RegistryKey & key)
{
	RegistryKeyNames subKeyNames;
	key.getKeyNames(rtl::OUString(), subKeyNames);
    for (sal_uInt32 i = 0; i < subKeyNames.getLength(); ++i)
    {
        rtl::OUString keyName(subKeyNames.getElement(i));
        if (!options.matchedWithExcludeKey(keyName))
        {
            keyName = keyName.copy(keyName.lastIndexOf('/') + 1);
            RegistryKey subKey;
            if (!key.openKey(keyName, subKey))
            {
                if (options.forceOutput())
                {
                    fprintf(
                        stdout,
                        ("WARNING: could not open key \"%s\" in registry"
                         " \"%s\"\n"),
                        U2S(subKeyNames.getElement(i)),
                        options.getRegName1().c_str());
                }
            }
            if (subKey.isValid())
            {
                RegValueType type;
                sal_uInt32 size;
                if (subKey.getValueInfo(rtl::OUString(), &type, &size) != REG_NO_ERROR)
                {
                    if (options.forceOutput())
                    {
                        fprintf(
                            stdout,
                            ("WARNING: could not read key \"%s\" in registry"
                             " \"%s\"\n"),
                            U2S(subKeyNames.getElement(i)),
                            options.getRegName1().c_str());
                    }
                }
                else if (type == RG_VALUETYPE_BINARY)
                {
                    bool published = false;
                    std::vector< sal_uInt8 > value(size);
                    if (subKey.getValue(rtl::OUString(), &value[0]) != REG_NO_ERROR)
                    {
                        if (options.forceOutput())
                        {
                            fprintf(
                                stdout,
                                ("WARNING: could not read key \"%s\" in"
                                 " registry \"%s\"\n"),
                                U2S(subKeyNames.getElement(i)),
                                options.getRegName1().c_str());
                        }
                    }
                    else
                    {
                        published = typereg::Reader(&value[0], value.size(), false, TYPEREG_VERSION_1).isPublished();
                    }
                    if (published)
                    {
                        return true;
                    }
                }
            }
        }
    }
    return false;
}

static sal_uInt32 checkDifferences(
    Options_Impl const & options,
    RegistryKey& key, StringSet& keys,
    RegistryKeyNames& subKeyNames1,
    RegistryKeyNames& subKeyNames2)
{
	sal_uInt32 nError = 0;
	sal_uInt32 length1 = subKeyNames1.getLength();
	sal_uInt32 length2 = subKeyNames2.getLength();
	sal_uInt32 i,j;

	for (i=0; i<length1; i++)
	{
        sal_Bool bFound = sal_False;
		for (j=0; j<length2; j++)
		{
			if ( subKeyNames1.getElement(i) == subKeyNames2.getElement(j) )
			{
				bFound = sal_True;
				keys.insert(subKeyNames1.getElement(i));
				break;					
			}
		}
		if ( !bFound )
		{
            if ( options.fullCheck() )
            {
                if ( options.forceOutput() )
                {
                    fprintf(stdout, "EXISTENCE: key \"%s\" exists only in registry \"%s\"\n",
                            U2S(subKeyNames1.getElement(i)), options.getRegName1().c_str());
                }
                nError++;
            }
            else
            {
                rtl::OUString keyName(subKeyNames1.getElement(i));
                if (!options.matchedWithExcludeKey(keyName))
                {
                    keyName = keyName.copy(keyName.lastIndexOf('/') + 1);
                    RegistryKey subKey;
                    if (key.openKey(keyName, subKey))
                    {
                        if (options.forceOutput())
                        {
                            fprintf(
                                stdout,
                                ("ERROR: could not open key \"%s\" in registry"
                                 " \"%s\"\n"),
                                U2S(subKeyNames1.getElement(i)),
                                options.getRegName1().c_str());
                        }
                        ++nError;
                    }
                    if (subKey.isValid())
                    {
                        RegValueType type;
                        sal_uInt32 size;
                        if (subKey.getValueInfo(rtl::OUString(), &type, &size) != REG_NO_ERROR)
                        {
                            if (options.forceOutput())
                            {
                                fprintf(
                                    stdout,
                                    ("ERROR: could not read key \"%s\" in"
                                     " registry \"%s\"\n"),
                                    U2S(subKeyNames1.getElement(i)),
                                    options.getRegName1().c_str());
                            }
                            ++nError;
                        }
                        else if (type == RG_VALUETYPE_BINARY)
                        {
                            std::vector< sal_uInt8 > value(size);
                            if (subKey.getValue(rtl::OUString(), &value[0]) != REG_NO_ERROR)
                            {
                                if (options.forceOutput())
                                {
                                    fprintf(
                                        stdout,
                                        ("ERROR: could not read key \"%s\" in"
                                         " registry \"%s\"\n"),
                                        U2S(subKeyNames1.getElement(i)),
                                        options.getRegName1().c_str());
                                }
                                ++nError;
                            }
                            else
                            {
                                typereg::Reader reader(&value[0], value.size(), false, TYPEREG_VERSION_1);
                                if (reader.getTypeClass() == RT_TYPE_MODULE)
                                {
                                    if (options.checkUnpublished() || hasPublishedChildren(options, subKey))
                                    {
                                        if (options.forceOutput())
                                        {
                                            fprintf(
                                                stdout,
                                                ("EXISTENCE: module \"%s\""
                                                 " %sexists only in registry"
                                                 " 1\n"),
                                                U2S(subKeyNames1.getElement(i)),
                                                (options.checkUnpublished()
                                                 ? ""
                                                 : "with published children "));
                                        }
                                        ++nError;
                                    }
                                }
                                else if (options.checkUnpublished() || reader.isPublished())
                                {
                                    if (options.forceOutput())
                                    {
                                        fprintf(
                                            stdout,
                                            ("EXISTENCE: %spublished key \"%s\""
                                             " exists only in registry 1\n"),
                                            reader.isPublished() ? "" : "un",
                                            U2S(subKeyNames1.getElement(i)));
                                    }
                                    ++nError;
                                }
                            }
                        }
                    }
                }
            }
		}
	}

	for (i=0; i<length2; i++)
	{
        sal_Bool bFound = sal_False;
		for (j=0; j<length1; j++)
		{
			if ( subKeyNames2.getElement(i) == subKeyNames1.getElement(j) )
			{
				bFound = sal_True;
				keys.insert(subKeyNames2.getElement(i));
				break;
			}
		}
		if ( !bFound && options.fullCheck() )
		{
			if ( options.forceOutput() )
			{
				fprintf(stdout, "EXISTENCE: key \"%s\" exists only in registry \"%s\"\n",
						U2S(subKeyNames2.getElement(i)), options.getRegName2().c_str());
			}
			nError++;
		}
	}
	return nError;
}	

static sal_uInt32 compareKeys(
    Options_Impl const & options,
    RegistryKey& key1,
    RegistryKey& key2)
{
	sal_uInt32 nError = 0;
	
	RegValueType valueType1 = RG_VALUETYPE_NOT_DEFINED;
	RegValueType valueType2 = RG_VALUETYPE_NOT_DEFINED;
	sal_uInt32 size1 = 0;
	sal_uInt32 size2 = 0;

	OUString tmpName;
	RegError e1 = key1.getValueInfo(tmpName, &valueType1, &size1);
	RegError e2 = key2.getValueInfo(tmpName, &valueType2, &size2);
	if ( (e1 == e2) && (e1 != REG_VALUE_NOT_EXISTS) && (e1 != REG_INVALID_VALUE) )
	{
		nError += checkValueDifference(options, key1, valueType1, size1, key2, valueType2, size2);	
	}
    else
	{
		if ( (e1 != REG_INVALID_VALUE) || (e2 != REG_INVALID_VALUE) )
		{
			if ( options.forceOutput() )
			{
				fprintf(stdout, "VALUES: key values of key \"%s\" are different\n", U2S(key1.getName()));
			}
			nError++;
		}
	}
	
	RegistryKeyNames subKeyNames1;
	RegistryKeyNames subKeyNames2;

	key1.getKeyNames(tmpName, subKeyNames1);
	key2.getKeyNames(tmpName, subKeyNames2);

	StringSet keys;
	nError += checkDifferences(options, key1, keys, subKeyNames1, subKeyNames2);

	StringSet::iterator iter = keys.begin();
	StringSet::iterator end = keys.end();

	while ( iter !=  end )
	{
        OUString keyName(*iter);
		if ( options.matchedWithExcludeKey(keyName) )
		{
			++iter;
			continue;
		}

        sal_Int32 nPos = keyName.lastIndexOf( '/' );
        keyName = keyName.copy( nPos != -1 ? nPos+1 : 0 );

        RegistryKey subKey1;
		if ( key1.openKey(keyName, subKey1) )
		{	
			if ( options.forceOutput() )
			{
				fprintf(stdout, "ERROR: could not open key \"%s\" in registry \"%s\"\n",
						U2S(*iter), options.getRegName1().c_str());
			}
			nError++;
		}

        RegistryKey subKey2;
		if ( key2.openKey(keyName, subKey2) )
		{
			if ( options.forceOutput() )
			{
				fprintf(stdout, "ERROR: could not open key \"%s\" in registry \"%s\"\n",
						U2S(*iter), options.getRegName2().c_str());
			}
			nError++;
		}

		if ( subKey1.isValid() && subKey2.isValid() )
		{
			nError += compareKeys(options, subKey1, subKey2);
		}
		++iter;
	}

	return nError;
}	

#if (defined UNX) || (defined OS2) || defined __MINGW32__
int main( int argc, char * argv[] )
#else
int _cdecl main( int argc, char * argv[] )
#endif
{
    std::vector< std::string > args;

    Options_Impl options(argv[0]);
    for (int i = 1; i < argc; i++)
    {
        if (!Options::checkArgument(args, argv[i], strlen(argv[i])))
        {
            // failure.
            options.printUsage();
            return (1);
        }
    }
	if (!options.initOptions(args))
	{
		return (1);
	}

	OUString regName1( convertToFileUrl(options.getRegName1().c_str(), options.getRegName1().size()) );
	OUString regName2( convertToFileUrl(options.getRegName2().c_str(), options.getRegName2().size()) );
	
	Registry reg1, reg2;
	if ( reg1.open(regName1, REG_READONLY) )
	{
		fprintf(stdout, "%s: open registry \"%s\" failed\n",
				options.getProgramName().c_str(), options.getRegName1().c_str());
		return (2);
	}
	if ( reg2.open(regName2, REG_READONLY) )
	{
		fprintf(stdout, "%s: open registry \"%s\" failed\n",
				options.getProgramName().c_str(), options.getRegName2().c_str());
		return (3);
	}

	RegistryKey key1, key2;
	if ( reg1.openRootKey(key1) )
	{
		fprintf(stdout, "%s: open root key of registry \"%s\" failed\n",
				options.getProgramName().c_str(), options.getRegName1().c_str());
		return (4);
	}
	if ( reg2.openRootKey(key2) )
	{
		fprintf(stdout, "%s: open root key of registry \"%s\" failed\n",
				options.getProgramName().c_str(), options.getRegName2().c_str());
		return (5);
	}

	if ( options.isStartKeyValid() )
	{
		if ( options.matchedWithExcludeKey( options.getStartKey() ) )
		{
			fprintf(stdout, "%s: start key is equal to one of the exclude keys\n",
					options.getProgramName().c_str());
			return (6);
		}
		RegistryKey sk1, sk2;
		if ( key1.openKey(options.getStartKey(), sk1) )
		{
			fprintf(stdout, "%s: open start key of registry \"%s\" failed\n",
					options.getProgramName().c_str(), options.getRegName1().c_str());
			return (7);
		}
		if ( key2.openKey(options.getStartKey(), sk2) )
		{
			fprintf(stdout, "%s: open start key of registry \"%s\" failed\n",
					options.getProgramName().c_str(), options.getRegName2().c_str());
			return (8);
		}
		
		key1 = sk1;
		key2 = sk2;
	}
		
	sal_uInt32 nError = compareKeys(options, key1, key2);
	if ( nError )
	{
		if ( options.unoTypeCheck() )
		{
			fprintf(stdout, "%s: registries are incompatible: %lu differences!\n", 
					options.getProgramName().c_str(),
                    sal::static_int_cast< unsigned long >(nError));
		}
        else
		{
			fprintf(stdout, "%s: registries contain %lu differences!\n", 
					options.getProgramName().c_str(),
                    sal::static_int_cast< unsigned long >(nError));
		}
	}
    else
	{
		if ( options.unoTypeCheck() )
		{
			fprintf(stdout, "%s: registries are compatible!\n", 
					options.getProgramName().c_str());
		}
        else
		{
			fprintf(stdout, "%s: registries are equal!\n", 
					options.getProgramName().c_str());
		}
	}

	key1.releaseKey();
	key2.releaseKey();
	if ( reg1.close() )
	{
		fprintf(stdout, "%s: closing registry \"%s\" failed\n",
				options.getProgramName().c_str(), options.getRegName1().c_str());
        return (9);
	}
	if ( reg2.close() )
	{
		fprintf(stdout, "%s: closing registry \"%s\" failed\n",
				options.getProgramName().c_str(), options.getRegName2().c_str());
        return (10);
	}

	return ((nError > 0) ? 11 : 0);
}