/**************************************************************
 * 
 * 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.
 * 
 *************************************************************/



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

#include "registry/registry.hxx"
#include "registry/reflread.hxx"
#include "fileurl.hxx"
#include "options.hxx"

#include "rtl/ustring.hxx"
#include "osl/diagnose.h"

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

#include <vector>
#include <string>

using namespace rtl;
using namespace registry::tools;

#define U2S( s ) \
	OUStringToOString(s, RTL_TEXTENCODING_UTF8).getStr()
#define S2U( s ) \
	OStringToOUString(s, RTL_TEXTENCODING_UTF8)

class Options_Impl : public Options
{
public:
	explicit Options_Impl(char const * program)
		: Options (program), m_bForceOutput(false)
		{}

    std::string const & getIndexReg() const
        { return m_indexRegName; }
    std::string const & getTypeReg() const
		{ return m_typeRegName; }
	bool hasBase() const
        { return (m_base.getLength() > 0); }
	const OString & getBase() const
		{ return m_base; }
	bool forceOutput() const
		{ return m_bForceOutput; }	

protected:
	virtual void printUsage_Impl() const;
    virtual bool initOptions_Impl (std::vector< std::string > & rArgs);
	
    std::string m_indexRegName;
    std::string m_typeRegName;
	OString		m_base;
    bool m_bForceOutput;
};	

// virtual
void Options_Impl::printUsage_Impl() const
{
    std::string const & rProgName = getProgramName();
    fprintf(stderr,
            "Usage: %s -r<filename> -o<filename> [-options] | @<filename>\n", rProgName.c_str()
            );
    fprintf(stderr,
            "    -o<filename>  = filename specifies the name of the new singleton index registry.\n"
            "    -r<filename>  = filename specifies the name of the type registry.\n"
            "    @<filename>   = filename specifies a command file.\n"
            "Options:\n"
            "    -b<name>  = name specifies the name of a start key. The types will be searched\n"
            "                under this key in the type registry.\n"
            "    -f        = force the output of all found singletons.\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)
    {
        std::string option (*first);
        if ((*first)[0] != '-')
        {
            return badOption("invalid", option.c_str());
        }
        switch ((*first)[1])
        {
        case 'r':
        case 'R':
            {
                if (!((++first != last) && ((*first)[0] != '-')))
                {
                    return badOption("invalid", option.c_str());
                }
                m_typeRegName = OString((*first).c_str(), (*first).size());
                break;
            }
        case 'o':
        case 'O':
            {
                if (!((++first != last) && ((*first)[0] != '-')))
                {
                    return badOption("invalid", option.c_str());
                }
                m_indexRegName = (*first);
                break;
            }
        case 'b':
        case 'B':
            {
                if (!((++first != last) && ((*first)[0] != '-')))
                {
                    return badOption("invalid", option.c_str());
                }
                m_base = OString((*first).c_str(), (*first).size());
                break;
            }
        case 'f':
        case 'F':
            {
			    if ((*first).size() > 2)
                {
                    return badOption("invalid", option.c_str());
                }
                m_bForceOutput = sal_True;
                break;
            }
        case 'h':
        case '?':
            {
			    if ((*first).size() > 2)
                {
                    return badOption("invalid", option.c_str());
                }
                return printUsage();
                // break; // unreachable
            }
        default:
            return badOption("unknown", option.c_str());
            // break; // unreachable
        }
    }
    return true;
}

static sal_Bool checkSingletons(Options_Impl const & options, RegistryKey& singletonKey, RegistryKey& typeKey)
{
	RegValueType valueType = RG_VALUETYPE_NOT_DEFINED;
	sal_uInt32 size = 0;
	OUString tmpName;
    sal_Bool bRet = sal_False;
    
	RegError e = typeKey.getValueInfo(tmpName, &valueType, &size);
    if ((e != REG_VALUE_NOT_EXISTS) && (e != REG_INVALID_VALUE) && (valueType == RG_VALUETYPE_BINARY))
	{
        std::vector< sal_uInt8 > value(size);
        typeKey.getValue(tmpName, &value[0]); // @@@ broken api: write to buffer w/o buffer size.
		
		RegistryTypeReader reader(&value[0], value.size(), sal_False);
		if ( reader.isValid() && reader.getTypeClass() == RT_TYPE_SINGLETON )
        {
            RegistryKey entryKey;
			OUString    singletonName = reader.getTypeName().replace('/', '.');
			if ( singletonKey.createKey(singletonName, entryKey) )
            {
				fprintf(stderr, "%s: could not create SINGLETONS entry for \"%s\"\n",
					options.getProgramName().c_str(), U2S( singletonName ));
            }
            else
            {
				bRet = sal_True;
                OUString value2 = reader.getSuperTypeName();
				
				if ( entryKey.setValue(tmpName, RG_VALUETYPE_UNICODE,
                					   (RegValue)value2.getStr(), sizeof(sal_Unicode)* (value2.getLength()+1)) )
				{
					fprintf(stderr, "%s: could not create data entry for singleton \"%s\"\n",
							options.getProgramName().c_str(), U2S( singletonName ));
				}

				if ( options.forceOutput() )
				{
					fprintf(stderr, "%s: create SINGLETON entry for \"%s\" -> \"%s\"\n",
							options.getProgramName().c_str(), U2S( singletonName ), U2S(value2));
            	}
            }
        }
	}
    
   	RegistryKeyArray subKeys;
	typeKey.openSubKeys(tmpName, subKeys);

	sal_uInt32 length = subKeys.getLength();
	for (sal_uInt32 i = 0; i < length; i++)
	{
        RegistryKey elementKey = subKeys.getElement(i);
		if ( checkSingletons(options, singletonKey, elementKey) )
		{
			bRet = sal_True;
		}
	}
	return bRet;
}	

#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;
    for (int i = 1; i < argc; i++)
    {
        int result = Options::checkArgument(args, argv[i], strlen(argv[i]));
        if (result != 0)
        {
            // failure.
            return (result);
        }
    }

    Options_Impl options(argv[0]);
    if (!options.initOptions(args))
	{
        options.printUsage();
		return (1);
	}

	OUString indexRegName( convertToFileUrl(options.getIndexReg().c_str(), options.getIndexReg().size()) );
	Registry indexReg;
	if ( indexReg.open(indexRegName, REG_READWRITE) )
	{
		if ( indexReg.create(indexRegName) )
		{
			fprintf(stderr, "%s: open registry \"%s\" failed\n",
					options.getProgramName().c_str(), options.getIndexReg().c_str());
			return (2);
        }
	}

	OUString typeRegName( convertToFileUrl(options.getTypeReg().c_str(), options.getTypeReg().size()) );
	Registry typeReg;
	if ( typeReg.open(typeRegName, REG_READONLY) )
	{
		fprintf(stderr, "%s: open registry \"%s\" failed\n",
				options.getProgramName().c_str(), options.getTypeReg().c_str());
		return (3);
	}

	RegistryKey indexRoot;
	if ( indexReg.openRootKey(indexRoot) )
	{
		fprintf(stderr, "%s: open root key of registry \"%s\" failed\n",
				options.getProgramName().c_str(), options.getIndexReg().c_str());
		return (4);
	}

	RegistryKey typeRoot;
	if ( typeReg.openRootKey(typeRoot) )
	{
		fprintf(stderr, "%s: open root key of registry \"%s\" failed\n",
				options.getProgramName().c_str(), options.getTypeReg().c_str());
		return (5);
	}

	RegistryKey typeKey;
	if ( options.hasBase() )
	{
		if ( typeRoot.openKey(S2U(options.getBase()), typeKey) )
		{
			fprintf(stderr, "%s: open base key of registry \"%s\" failed\n",
					options.getProgramName().c_str(), options.getTypeReg().c_str());
			return (6);
		}
	}
    else
    {
    	typeKey = typeRoot;
    }
		
	RegistryKey singletonKey;
	if ( indexRoot.createKey(OUString::createFromAscii("SINGLETONS"), singletonKey) )
	{
		fprintf(stderr, "%s: open/create SINGLETONS key of registry \"%s\" failed\n",
				options.getProgramName().c_str(), options.getIndexReg().c_str());
		return (7);
	}

	sal_Bool bSingletonsExist = checkSingletons(options, singletonKey, typeKey);

	indexRoot.releaseKey();
	typeRoot.releaseKey();
    typeKey.releaseKey();
    singletonKey.releaseKey();
	if ( indexReg.close() )
	{
		fprintf(stderr, "%s: closing registry \"%s\" failed\n",
				options.getProgramName().c_str(), options.getIndexReg().c_str());
        return (9);
	}
	if ( !bSingletonsExist )
	{
		if ( indexReg.destroy(OUString()) )
		{
			fprintf(stderr, "%s: destroy registry \"%s\" failed\n",
					options.getProgramName().c_str(), options.getIndexReg().c_str());
	        return (10);
		}		
	}	
	if ( typeReg.close() )
	{
		fprintf(stderr, "%s: closing registry \"%s\" failed\n",
				options.getProgramName().c_str(), options.getTypeReg().c_str());
        return (11);
	}
}