// **********************************************************************
//
// Copyright (c) 1999
// Object Oriented Concepts, Inc.
// Billerica, MA, USA
//
// All Rights Reserved
//
// **********************************************************************

#include <OB/Basic.h>
#include <OB/Except.h>
#include <OB/Template.h>
#include <OB/Util.h>
#include <OB/Properties.h>

#include <stdlib.h>

#ifdef HAVE_FSTREAM
#   include <fstream>
#else
#   include <fstream.h>
#endif

OBProperties* OBProperties::instance_ = 0;

class OBPropertiesDestroyer
{
public:

    ~OBPropertiesDestroyer()
    {
	delete OBProperties::instance_;
	OBProperties::instance_ = 0;
    }
};

static OBPropertiesDestroyer destroyer;

// ----------------------------------------------------------------------
// OBProperties private member implementation
// ----------------------------------------------------------------------

void
OBProperties::load(const char* name)
{
    assert_nca(name, OBNCANullString);

    OBMessageViewer* viewer = OBMessageViewer::instance();

    ifstream in; // Must use open(name), not ifstream in(name) (VC++ bug)
    in.open(name);
    if(in.fail())
    {
	CORBA_String_var str =
        CORBA_string_dup("Properties: configuration file `");
	str += name;
	str += "' not found";
	viewer -> warning(str);

	return;
    }
    char ch;
    in.get(ch);

    CORBA_String_var key = CORBA_string_alloc(1024);
    CORBA_String_var value = CORBA_string_alloc(1024);

    while(in)
    {
        key[0] = '\0';
        value[0] = '\0';

        //
        // Find start of key
        //
        switch(ch)
        {
        case '#':
        case '!':
            do
            {
                in.get(ch);
            }
            while(in && ch != '\n' && ch != '\r');
            continue;
            
        case '\n':
        case '\r':
        case ' ':
        case '\t':
            in.get(ch);
            continue;
        }
        
        //
        // Read the key.
        //
        while (in &&
               (ch != '=') && (ch != ':') && 
               (ch != ' ') && (ch != '\t') &&
               (ch != '\n') && (ch != '\r'))
        {
            key += ch;
            in.get(ch);
        }
        while (in && ((ch == ' ') || (ch == '\t')))
            in.get(ch);
        if(in && ((ch == '=') || (ch == ':')))
            in.get(ch);
        while(in && ((ch == ' ') || (ch == '\t')))
            in.get(ch);

        //
        // Read the value
        //
        while (in && ((ch != '\n') && (ch != '\r')))
        {
            char next = 0;
            if (ch == '\\')
            {
                if(!in.get(ch))
                    continue;
                switch (ch)
                {
                case '\r':
                    in.get(ch);
                    if (in && ((ch == '\n') || (ch == ' ') || (ch == '\t')))
                    {
                        // fall thru to '\n' case
                    }
                    else
                        continue;
                    
                case '\n':
                    in.get(ch);
                    while (in && ((ch == ' ') || (ch == '\t')))
                        ;
                    continue;
                    
                case 't':
                    ch = '\t';
                    in.get(next);
                    break;
                    
                case 'n':
                    ch = '\n';
                    in.get(next);
                    break;
                    
                case 'r':
                    ch = '\r';
                    in.get(next);
                    break;
                    
                case 'u':
                    {
                        while(in && ch == 'u')
                            in.get(ch);
			int d = 0;
                        bool stop = false;
                        for (int i = 0 ; i < 4 && !stop; i++)
                        {
                            if(!in.get(ch))
                                break;
                            switch (ch)
                            {
                            case '0': case '1': case '2': case '3': case '4':
                            case '5': case '6': case '7': case '8': case '9':
                                d = (d << 4) + ch - '0';
                                break;
                            case 'a': case 'b': case 'c': case 'd': case 'e':
                            case 'f':
                                d = (d << 4) + 10 + ch - 'a';
                                break;
                            case 'A': case 'B': case 'C': case 'D': case 'E':
                            case 'F':
                                d = (d << 4) + 10 + ch - 'A';
                                break;
                            default:
                                stop = true;
                            }
                            if(!stop)
                                ch = next;
                        }
			ch = d;
			break;
                    }
                
                default:
                    in.get(next);
                    break;
                }
            }
            else
            {
                in.get(next);
            }
            value += ch;
            ch = next;
        }
        setProperty(key, value);
    }
}

#ifdef WIN32

// Add all values in this registry key, and all subkeys.
//
void
OBProperties::load(HKEY base, const char* path, const char* prefix)
{
    HKEY key;

    //
    // Open the registry.
    //
    DWORD rc = RegOpenKeyEx(base, path, 0, KEY_READ, &key);

    //
    // If the key doesn't exist, then return.
    //
    if(rc != ERROR_SUCCESS)
    {
	return;
    }

    // Buffer for class name.
    char className[MAX_PATH] = "";
    // Length of class string.
    DWORD szClassLen = MAX_PATH;
    // Number of sub keys.
    DWORD numSubKeys;
    // Longest sub key size.
    DWORD numMaxSubKey;
    // Longest class string.
    DWORD numMaxClass;
    // Number of values for this key.
    DWORD numValues;
    // Longest Value name.
    DWORD  maxValueName;
    // Longest Value data.
    DWORD  maxValueData;
    // Security descriptor.
    DWORD  secDesc;
    // Last write time.
    FILETIME lastWriteTime;

    //
    // Query registry information.
    //
    RegQueryInfoKey(key,               // Key handle.
		    className,         // Buffer for class name.
		    &szClassLen,       // Length of class string.
		    NULL,              // Reserved.
		    &numSubKeys,       // Number of sub keys.
		    &numMaxSubKey,     // Longest sub key size.
		    &numMaxClass,      // Longest class string.
		    &numValues,        // Number of values for this key.
		    &maxValueName,     // Longest Value name.
		    &maxValueData,     // Longest Value data.
		    &secDesc,          // Security descriptor.
		    &lastWriteTime);   // Last write time.

    //
    // We need to allocate one more byte for storage.
    //
    ++maxValueName;
    ++maxValueData;

    int i;
    DWORD szValueName;
    char* valueName = new char[maxValueName];
    DWORD szData;
    unsigned char* data = new unsigned char[maxValueData];
    DWORD type;

    //
    // Enumerate each value in this registry key.
    //
    for(i = 0, rc = ERROR_SUCCESS; rc == ERROR_SUCCESS; i++)
    {
	szValueName = maxValueName;
	valueName[0] = '\0';
	szData = maxValueData;
	rc = RegEnumValue(key, i, valueName,
			  &szValueName, 0,
			  &type, data, &szData);

	//
	// Did we get a valid key?
	//
	if(rc == ERROR_SUCCESS)
	{
	    //
	    // The key should be a string.
	    //
	    if(type == REG_SZ)
	    {
		//
		// Construct the property name.
		//
		char name[MAX_PATH];
		strcpy(name, prefix);
		strcat(name, ".");
		strcat(name, valueName);

		//
		// Set the property.
		//
		setProperty(name, (const char*)data);
	    }
	    else
	    {
		CORBA_String_var msg =
		    CORBA_string_dup("Registry value not REG_SZ: ");
		msg += path;
		msg += "\\";
		msg += valueName;
		OBMessageViewer::instance() -> warning(msg);
	    }
	}
    }
    delete[] valueName;
    delete[] data;

    //
    // Recurse down each set of subkeys.
    //
    char name[MAX_PATH];
    for(i = 0, rc = ERROR_SUCCESS; rc == ERROR_SUCCESS; i++)
    {
	rc = RegEnumKey(key, i, name, MAX_PATH);
	if(rc == ERROR_SUCCESS)
	{
	    //
	    // Construct the subkey path name.
	    //
	    char buf[MAX_PATH];
	    strcpy(buf, path);
	    strcat(buf, "\\");
	    strcat(buf, name);
	    
	    //
	    // Construct the subkey property prefix.
	    //
	    char pref[MAX_PATH];
	    if(*prefix != '\0')
	    {
		strcpy(pref, prefix);
		strcat(pref, ".");
	    }
	    else
	    {
		*pref = '\0';
	    }
	    strcat(pref, name);

	    //
	    // Add all values contained in this subkey.
	    //
	    load(base, buf, pref);
	}
    }

    //
    // Close the registry key.
    //
    RegCloseKey(key);
}
#endif

// ----------------------------------------------------------------------
// OBProperties constructor and destructor
// ----------------------------------------------------------------------

OBProperties::OBProperties()
{
    assert(instance_ == 0);
    instance_ = this;
}

OBProperties::~OBProperties()
{
    instance_ = 0;
}

// ----------------------------------------------------------------------
// OBProperties public member implementation
// ----------------------------------------------------------------------

void
OBProperties::setProperty(const char* key, const char* value)
{
    for(CORBA_ULong i = 0; i < props_.length(); i += 2)
        if(strcmp(props_[i], key) == 0)
        {
            props_[i+1] = CORBA_string_dup(value);
            return;
        }

    props_.append(key);
    props_.append(value);
}

const char*
OBProperties::getProperty(const char* key) const
{
    for(CORBA_ULong i = 0; i < props_.length(); i += 2)
        if(strcmp(props_[i], key) == 0)
            return props_[i+1];

    return 0;
}

OBProperties::KeySeq
OBProperties::getKeys(const char* prefix) const
{
    KeySeq keys;
    int prefLen = strlen(prefix);
    for(CORBA_ULong i = 0; i < props_.length(); i += 2)
        if(strncmp(props_[i], prefix, prefLen) == 0)
            keys.append(props_[i]);

    return keys;
}

OBProperties::KeySeq
OBProperties::getKeys() const
{
    KeySeq keys;
    for(CORBA_ULong i = 0; i < props_.length(); i += 2)
        keys.append(props_[i]);
    
    return keys;
}

OBProperties*
OBProperties::instance()
{
    return instance_;
}

OBProperties*
OBProperties::init(int& argc, char** argv
#ifdef WIN32
		   , bool addRegEntries
#endif
		  )
{
    OBProperties* properties = OBProperties::instance();
    if(properties != 0)
        return properties;

    OBMessageViewer* viewer = OBMessageViewer::instance();
    
    CORBA_String_var configFile;
    
    //
    // First scan through the argument list looking for the config
    // file variable. We need to do this since command line arguments
    // have precendence over the environment variables.
    //
    int i = 0;
    while(i < argc)
    {
        if(strcmp(argv[i], "-ORBconfig") == 0)
	{
	    if(i + 1 >= argc)
	    {
		viewer -> error("OBProperties::init: argument expected for "
                                "-ORBconfig");
		throw CORBA_INITIALIZE();
	    }
            configFile = CORBA_string_dup(argv[i + 1]);
	    for(int j = i ; j + 2 < argc ; j++)
		argv[j] = argv[j + 2];

	    argc -= 2;
	}
        else
            i++;
    }

    //
    // Create the properties
    //
    properties = new OBProperties();
    
    //
    // Read the configuration file
    //
    if(!configFile)
    {
        const char* env = getenv("ORBACUS_CONFIG");
        if(env != 0)
            configFile = CORBA_string_dup(env);
    }

#ifdef WIN32

    if(addRegEntries)
    {
	const char* base = "Software\\OOC\\Properties";

	//
	// Load properties from the registry. First from
	// HKEY_LOCAL_MACHINE, next from HKEY_CURRENT_USER.
	//
	properties -> load(HKEY_LOCAL_MACHINE, base, "");
	properties -> load(HKEY_CURRENT_USER, base, "");
    }
#endif

    if(configFile)
        properties -> load(configFile);

    return properties;
}
