/*
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * Copyright (c) 1999-2008 Apple Inc.  All Rights Reserved.
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 *
 */
/*
    File:       XMLPrefsParser.cpp

    Contains:   Prototype implementation of XMLPrefsParser object.


*/

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#ifndef __Win32__
#include <unistd.h>
#endif

#include "XMLPrefsParser.h"
#include "OSMemory.h"
#include "OSHeaders.h"

static const UInt32 kPrefArrayMinSize = 20;

static char* kMainTag = "CONFIGURATION";
static char* kServer = "SERVER";
static char* kModule = "MODULE";
static char* kPref = "PREF";
static char* kListPref = "LIST-PREF";
static char* kEmptyObject = "EMPTY-OBJECT";
static char* kObject = "OBJECT";
static char* kObjectList = "LIST-OBJECT";
static char* kValue = "VALUE";
static char* kNameAttr = "NAME";
static char* kTypeAttr = "TYPE";

static char* kFileHeader[] = 
{
    "<?xml version =\"1.0\"?>",
    "<!-- The Document Type Definition (DTD) for the file -->",
    "<!DOCTYPE CONFIGURATION [",
    "<!ELEMENT CONFIGURATION (SERVER, MODULE*)>",
    "<!ELEMENT SERVER (PREF|LIST-PREF|OBJECT|LIST-OBJECT)*>",
    "<!ELEMENT MODULE (PREF|LIST-PREF|OBJECT|LIST-OBJECT)*>",
    "<!ATTLIST MODULE",
    "\tNAME CDATA #REQUIRED>",
    "<!ELEMENT PREF (#PCDATA)>",
    "<!ATTLIST PREF",
    "\tNAME CDATA #REQUIRED",
    "\tTYPE (UInt8|SInt8|UInt16|SInt16|UInt32|SInt32|UInt64|SInt64|Float32|Float64|Bool16|Bool8|char) \"char\">",
    "<!ELEMENT LIST-PREF (VALUE*)>",
    "<!ELEMENT VALUE (#PCDATA)>",
    "<!ATTLIST LIST-PREF",
    "\tNAME CDATA #REQUIRED",
    "\tTYPE  (UInt8|SInt8|UInt16|SInt16|UInt32|SInt32|UInt64|SInt64|Float32|Float64|Bool16|Bool8|char) \"char\">",
    "<!ELEMENT OBJECT (PREF|LIST-PREF|OBJECT|LIST-OBJECT)*>",
    "<!ATTLIST OBJECT",
    "\tNAME CDATA #REQUIRED>",
    "<!ELEMENT LIST-OBJECT (OBJECT-VALUE*)>",
    "<!ELEMENT OBJECT-VALUE (PREF|LIST-PREF|OBJECT|LIST-OBJECT)*>",
    "<!ATTLIST LIST-OBJECT",
    "\tNAME CDATA #REQUIRED>",
    "]>",
    NULL
};

XMLPrefsParser::XMLPrefsParser(char* inPath)
:   XMLParser(inPath)
{}

XMLPrefsParser::~XMLPrefsParser()
{}


ContainerRef XMLPrefsParser::GetConfigurationTag()
{
    ContainerRef result = GetRootTag();
    if (result == NULL)
    {
        result = new XMLTag(kMainTag);
        SetRootTag(result);
    }
    
    return result;
}

ContainerRef XMLPrefsParser::GetRefForModule(char* inModuleName, Bool16 create)
{
    if (inModuleName == NULL)
        return GetRefForServer();
    
    ContainerRef result = GetConfigurationTag()->GetEmbeddedTagByNameAndAttr(kModule, kNameAttr, inModuleName);
    if (result == NULL)
    {
        result = new XMLTag(kModule);
        result->AddAttribute( kNameAttr, (char*)inModuleName);
        GetRootTag()->AddEmbeddedTag(result);
    }
    
    return result;
}

ContainerRef XMLPrefsParser::GetRefForServer()
{
    ContainerRef result = GetConfigurationTag()->GetEmbeddedTagByName(kServer);
    if (result == NULL)
    {
        result = new XMLTag(kServer);
        GetRootTag()->AddEmbeddedTag(result);
    }
    
    return result;
}

UInt32 XMLPrefsParser::GetNumPrefValues(ContainerRef pref)
{
    if (!strcmp(pref->GetTagName(), kPref))
    {
        if (pref->GetValue() == NULL)
            return 0;
        else
            return 1;
    }
    else if (!strcmp(pref->GetTagName(), kObject))
        return 1;
    else if (!strcmp(pref->GetTagName(), kEmptyObject))
        return 0;

    return pref->GetNumEmbeddedTags();  // it must be a list
}

UInt32 XMLPrefsParser::GetNumPrefsByContainer(ContainerRef container)
{
    return container->GetNumEmbeddedTags();
}

char* XMLPrefsParser::GetPrefValueByIndex(ContainerRef container, const UInt32 inPrefsIndex, const UInt32 inValueIndex,
                                            char** outPrefName, char** outDataType)
{
    if (outPrefName != NULL)
        *outPrefName = NULL;
    if (outPrefName != NULL)
        *outDataType = NULL;
    XMLTag* pref = container->GetEmbeddedTag(inPrefsIndex);
    if (pref == NULL)
        return NULL;
        
    return GetPrefValueByRef(pref, inValueIndex, outPrefName, outDataType);
}

char* XMLPrefsParser::GetPrefValueByRef(ContainerRef pref, const UInt32 inValueIndex,
                                            char** outPrefName, char** outDataType)
{
    if (outPrefName != NULL)
        *outPrefName = pref->GetAttributeValue(kNameAttr);
    if (outDataType != NULL)
    {
        *outDataType = pref->GetAttributeValue(kTypeAttr);
        if (*outDataType == NULL)
            *outDataType = "CharArray";
    }
    
    if (!strcmp(pref->GetTagName(), kPref))
    {
        if (inValueIndex > 0)
            return NULL;
        else
            return pref->GetValue();
    }
    
    if (!strcmp(pref->GetTagName(), kListPref))
    {
        XMLTag* value = pref->GetEmbeddedTag(inValueIndex);
        if (value != NULL)
            return value->GetValue();
    }
    
    if (!strcmp(pref->GetTagName(), kObject) || !strcmp(pref->GetTagName(), kObjectList))
        *outDataType = "QTSS_Object";
        
    return NULL;
}

ContainerRef XMLPrefsParser::GetObjectValue(ContainerRef pref, const UInt32 inValueIndex)
{
    if (!strcmp(pref->GetTagName(), kObject) && (inValueIndex == 0))
        return pref;
    if (!strcmp(pref->GetTagName(), kObjectList))
        return pref->GetEmbeddedTag(inValueIndex);
        
    return NULL;
}

ContainerRef XMLPrefsParser::GetPrefRefByName( ContainerRef container,
                                                    const char* inPrefName)
{
    return container->GetEmbeddedTagByAttr(kNameAttr, inPrefName);
}

ContainerRef XMLPrefsParser::GetPrefRefByIndex( ContainerRef container,
                                                    const UInt32 inPrefsIndex)
{
    return container->GetEmbeddedTag(inPrefsIndex);
}

ContainerRef XMLPrefsParser::AddPref( ContainerRef container, char* inPrefName,
                                      char* inPrefDataType )
{
    XMLTag* pref = container->GetEmbeddedTagByAttr(kNameAttr, inPrefName);
    if (pref != NULL)
        return pref;    // it already exists
        
    pref = NEW XMLTag(kPref);   // start it out as a pref
    pref->AddAttribute(kNameAttr, inPrefName);
    if (!strcmp(inPrefDataType, "QTSS_Object"))
        pref->SetTagName(kEmptyObject);
    else if (strcmp(inPrefDataType, "CharArray"))
        pref->AddAttribute(kTypeAttr, (char*)inPrefDataType);
    
    container->AddEmbeddedTag(pref);
    
    return pref;
}
                            
void XMLPrefsParser::AddPrefValue( ContainerRef pref, char* inNewValue)
{
    if (!strcmp(pref->GetTagName(), kPref))     // is this a PREF tag
    {
        if (pref->GetValue() == NULL)
        {
            // easy case, no existing value, so just add a vlue
            pref->SetValue(inNewValue);
            return;
        }
        else
        {
            // it already has a value, so change the pref to be a list pref and go to code below
            char* firstValue = pref->GetValue();
            XMLTag* value = NEW XMLTag(kValue);
            value->SetValue(firstValue);

            pref->SetTagName(kListPref);
            pref->SetValue(NULL);
            pref->AddEmbeddedTag(value);
        }
    }
    
    // we want to fall through from second case above, so this isn't an else
    if (!strcmp(pref->GetTagName(), kListPref))
    {
        XMLTag* value = NEW XMLTag(kValue);
        value->SetValue(inNewValue);
        pref->AddEmbeddedTag(value);
    }
}

void XMLPrefsParser::AddNewObject( ContainerRef pref )
{
    if (!strcmp(pref->GetTagName(), kEmptyObject))
    {
        // just flag that this is now a real object instead of a placeholder
        pref->SetTagName(kObject);
        return;
    }
    
    if (!strcmp(pref->GetTagName(), kObject))
    {
        // change the object to be an object list and go to code below
        XMLTag* subObject = NEW XMLTag(kObject);
        XMLTag* objectPref;
        // copy all this objects tags into the new listed object
        while((objectPref = pref->GetEmbeddedTag()) != NULL)
        {
            pref->RemoveEmbeddedTag(objectPref);
            subObject->AddEmbeddedTag(objectPref);
        }

        pref->SetTagName(kObjectList);
        pref->AddEmbeddedTag(subObject);
    }
    
    // we want to fall through from second case above, so this isn't an else
    if (!strcmp(pref->GetTagName(), kObjectList))
    {
        XMLTag* subObject = NEW XMLTag(kObject);
        pref->AddEmbeddedTag(subObject);
    }
}

void XMLPrefsParser::ChangePrefType( ContainerRef pref, char* inNewPrefDataType)
{
    pref->RemoveAttribute(kTypeAttr);   // remove it if it exists
    if (strcmp(inNewPrefDataType, "CharArray"))
        pref->AddAttribute(kTypeAttr, inNewPrefDataType);
}
                            
void XMLPrefsParser::SetPrefValue( ContainerRef pref, const UInt32 inValueIndex,
                                        char* inNewValue)
{
    UInt32 numValues = GetNumPrefValues(pref);
    
    if (((numValues == 0) || (numValues == 1)) && (inValueIndex == 0))
    {
        pref->SetValue(inNewValue);
    }
    else if (inValueIndex == numValues) // this is an additional value
        AddPrefValue(pref, inNewValue);
    else
    {
        XMLTag* value = pref->GetEmbeddedTag(inValueIndex);
        if (value != NULL)
            value->SetValue(inNewValue);
    }
}
        
void XMLPrefsParser::RemovePrefValue( ContainerRef pref, const UInt32 inValueIndex)
{
    UInt32 numValues = GetNumPrefValues(pref);
    if (inValueIndex >= numValues)
        return;
        
    if (numValues == 1)
    {
        delete pref;    // just remove the whole pref
    }
    else if (numValues == 2)
    {
        XMLTag* value = pref->GetEmbeddedTag(inValueIndex); // get the one we're removing
        delete value;                                       // delete it
        value = pref->GetEmbeddedTag(0);         			// get the remaining tag index always 0 for 2 vals
        pref->RemoveEmbeddedTag(value);                     // pull it out of the parent
        if (!strcmp(pref->GetTagName(), kObjectList))
        {
            pref->SetTagName(kObject);  // set it back to a simple pref
            // move all this objects tags into the parent
            XMLTag* objectPref;
            while((objectPref = value->GetEmbeddedTag()) != NULL)
            {
                value->RemoveEmbeddedTag(objectPref);
                pref->AddEmbeddedTag(objectPref);
            }
        }
        else
        {
            char* temp = value->GetValue();
            pref->SetTagName(kPref);    // set it back to a simple pref
            pref->SetValue(temp);
        }
        
        delete value;   // get rid of the other one
    }
    else
    {
        XMLTag* value = pref->GetEmbeddedTag(inValueIndex);
        if (value)
            delete value;
    }
}

void XMLPrefsParser::RemovePref( ContainerRef pref )
{
    delete pref;
}

int XMLPrefsParser::Parse()
{
    char error[500];
    
    if (!ParseFile(error, sizeof(error)))
    {
        qtss_printf("%s\n", error);
        return -1;
    }
    
    
    
    // the above routine checks that it's a valid XML file, we should check that
    // all the tags conform to our prefs format
    
    return 0;
}

int XMLPrefsParser::WritePrefsFile()
{
    GetConfigurationTag();  // force it to be created if it doesn't exist
    WriteToFile(kFileHeader);
    return 0;
}
