//Editor-Info: -*- C++ -*-
//
//Subject: TOVE project
//
//File: storage.cpp
//
//State: $State: Exp $
//
//Version: $Revision: 1.46 $
//
//Date: $Date: 1999/03/08 11:05:16 $
//
//Organisation:
//      Helsinki University of Technology
//      Laboratory of Telecommunications Software and Multimedia
//
//Authors:
//      Jari Katajavuori
//	Vesa-Matti Puro
//      Sami Raatikainen
//
//Description:
//      See header file (storage.h) description.
//
//Copyright:    
//      Copyright 1999 Helsinki University of Technology
//      ALL RIGHTS RESERVED BETWEEN JANUARY 1996 AND JUNE 1999.
//      
//Licence:
//     
//
//History:
//
//

#include "storage.h"
#include "debug.h"

//
// Method: Class DataContainer constructors
//
// Description:
//
//

pfStorage :: DataContainer :: DataContainer(void)
    : _type(UNDEFINED),
      _integer(0),
      _bitString(),
      _frame(),
      _string(),
      _storage(0),
//      _ie(0),
      _valuePresent(0),
      _multivariable(0)
{
    return;
}

pfStorage :: DataContainer 
    :: DataContainer(const DataContainer &other_)
{
    _type = other_._type;
    _bitString = other_._bitString,
    _frame = other_._frame;
    _integer = other_._integer;
    _string = other_._string;
    _storage = 0;
    if (other_._storage != 0)
    {
        _storage = new pfStorage(*other_._storage);
    }
    /*
    _ie = 0;
    if (other_._ie != 0)
    {
        _ie = (other_._ie)->clone();
    }
    */
    _valuePresent = other_._valuePresent;
    _multivariable = other_._multivariable;
    return;
}

pfStorage::DataContainer &
    pfStorage :: DataContainer
        :: operator=(const DataContainer &other_)
{
    if (this != &other_)
    {
        _type = other_._type;
        _bitString = other_._bitString;
        _frame = other_._frame;
        _integer = other_._integer;
        _string = other_._string;
        // Replace storage
        delete _storage;
        _storage = 0;
        if (other_._storage != 0)
        {
            _storage = new pfStorage(*other_._storage);
        }
        // Replace ie
        /*
        delete _ie;
        _ie = 0;
        if (other_._ie != 0)
        {
            _ie = (other_._ie)->clone();
        }
        */
        _valuePresent = other_._valuePresent;
        _multivariable = other_._multivariable;
    }
    return *this;
}


//
// Method: Class DataContainer destructor
//
// Description:
//      Classes must be deleted here.
//

pfStorage::DataContainer :: ~DataContainer(void)
{
    delete _storage;
    //delete _ie;
    return;
}

//
// Method: create
//
// Description:
//      Creates a new container setting its type. These are
//      used to prevent exceptions during initialization in
//      constructors and to avoid public use of pfStorageDataType.
//

pfStorage::DataContainer
    pfStorage::DataContainer :: createBitString(void)
{
    DataContainer result;
    result._type = BITSTRINGTYPE;
    return result;
}

pfStorage::DataContainer
    pfStorage::DataContainer :: createBoolean(void)
{
    DataContainer result;
    result._type = BOOLEANTYPE;
    return result;
}

pfStorage::DataContainer
    pfStorage::DataContainer :: createFrame(void)
{
    DataContainer result;
    result._type = FRAMETYPE;
    return result;
}

pfStorage::DataContainer
    pfStorage::DataContainer :: createHexString(void)
{
    DataContainer result;
    result._type = HEXSTRINGTYPE;
    return result;
}

pfStorage::DataContainer
    pfStorage::DataContainer :: createInteger(void)
{
    DataContainer result;
    result._type = INTTYPE;
    return result;
}

pfStorage::DataContainer
    pfStorage::DataContainer :: createString(void)
{
    DataContainer result;
    result._type = STRINGTYPE;
    return result;
}

pfStorage::DataContainer
    pfStorage::DataContainer :: createStorage(void)
{
    DataContainer result;
    result._type = STORAGETYPE;
    result._storage = new pfStorage;
    return result;
}

/*
pfStorage::DataContainer
    pfStorage::DataContainer :: createIE(void)
{
    DataContainer result;
    result._type = IETYPE;
    return result;
}
*/

//
// Method: Set
//
// Description:
//      Sets new value into the variable after checking the
//      type. pfInvalidTypeException is thrown if wrong type
//      is encountered.
//

void pfStorage::DataContainer :: setBitString(pfBitString &bitStringValue_)
{
    checkType(BITSTRINGTYPE);
    _bitString = bitStringValue_;
    _valuePresent = 1;
    return;
}

void pfStorage::DataContainer :: setBooleanFalse(void)
{
    checkType(BOOLEANTYPE);
    _integer = 0;
    _valuePresent = 1;
    return;
}

void pfStorage::DataContainer :: setBooleanTrue(void)
{
    checkType(BOOLEANTYPE);
    _integer = 1;
    _valuePresent = 1;
    return;
}

void pfStorage::DataContainer :: setFrame(pfFrame &frameValue_)
{
    checkType(FRAMETYPE);
    _frame = frameValue_;
    _valuePresent = 1;
    return;
}

void pfStorage::DataContainer :: setHexString(pfFrame &frameValue_)
{
    checkType(HEXSTRINGTYPE);
    _frame = frameValue_;
    _valuePresent = 1;
    return;
}

void pfStorage::DataContainer :: setInteger(pfUlong integerValue_)
{
    checkType(INTTYPE);
    _integer = integerValue_;
    _valuePresent = 1;
    return;
}

void pfStorage::DataContainer :: setString(const string &stringValue_)
{
    checkType(STRINGTYPE);
    _string = stringValue_;
    _valuePresent = 1;
    return;
}

void pfStorage::DataContainer :: setStorage(pfStorage &storageValue_)
{
    checkType(STORAGETYPE);
    // Check the given value is not a reference to the same instance
    // already in container.
    if (_storage != &storageValue_)
    {
        delete _storage;
        _storage = new pfStorage(storageValue_);
        _valuePresent = 1;
    }
    return;
}

/*
void pfStorage::DataContainer :: setIE(pfIE *ieValue_)
{
    checkType(IETYPE);
    // Check the given value is not a reference to the same instance
    // already in container.
    if (_ie != ieValue_)
    {
        delete _ie;
        _ie = ieValue_;
        _valuePresent = 1;
    }
    return;
}

void pfStorage::DataContainer :: setIE(auto_ptr<pfIE> &ieValue_)
{
    checkType(IETYPE);
    // Check the given value is not a reference to the same instance
    // already in container.
    if (_ie != ieValue_.get())
    {
        delete _ie;
        _ie = ieValue_.release();
        _valuePresent = 1;
    }
    return;
}
*/

//
// Method: Get
//
// Description:
//      Gets data. pfInvalidTypeException is thrown if
//      encounters wrong data type.
//

pfBitString pfStorage::DataContainer :: getBitString(void) const
{
    checkType(BITSTRINGTYPE);
    return _bitString;
}

bool pfStorage::DataContainer :: getBoolean(void) const
{
    checkType(BOOLEANTYPE);
    return _integer ? 1 : 0;
}

pfFrame pfStorage::DataContainer :: getFrame(void) const
{
    checkType(FRAMETYPE);
    return _frame;
}

pfFrame pfStorage::DataContainer :: getHexString(void) const
{
    checkType(HEXSTRINGTYPE);
    return _frame;
}

pfUlong pfStorage::DataContainer :: getInteger(void) const
{
    checkType(INTTYPE);
    return _integer;
}

string pfStorage::DataContainer :: getString(void) const
{
    checkType(STRINGTYPE);
    return _string;
}

pfStorage &pfStorage::DataContainer :: getStorage(void) const
{
    checkType(STORAGETYPE);
    return *_storage;
}

/*
pfIE *pfStorage::DataContainer :: getIE(void) const
{
    checkType(IETYPE);
    return _ie;
}

pfIE *pfStorage::DataContainer :: adoptIE(void)
{
    checkType(IETYPE);
    pfIE *ie = _ie;
    _ie = 0;
    return ie;
}
*/


//
// Method: isValuePresent
//
// Description:
//      Returns non-zero if the variable has a value.
//

bool pfStorage :: DataContainer :: isValuePresent(void) const
{
    return _valuePresent;
}

//
// Method: getType
//
// Description:
//      Returns the type of the data
//

pfStorage::pfStorageDataType 
pfStorage :: DataContainer :: getType(void) const
{
    return _type;
}

void pfStorage::DataContainer
    :: checkType(pfStorageDataType type_) const
{
    if (_type != type_)
    {
        throw pfInvalidTypeException(PF_EX_INFO);
    }
    return;
}

//
// Method: setMultivariable
//
// Description:
//      Sets variable to be multivariable (several variables of the
//      same name allowed)
//

void pfStorage::DataContainer :: setMultivariable(void)
{
    _multivariable = 1;
    return;
}

//
// Method: isMultivariable
//
// Description:
//      Returns non-zero if variable is multivariable
//

bool pfStorage::DataContainer :: isMultivariable(void) const
{
    return _multivariable;
}

//
// Method: Class pfStorage constructors
//
// Description:
//      

pfStorage :: pfStorage(void)
    : pfIEcontainer(),
      _data(),
      _iter(_data.begin())
{
    return;
}

pfStorage :: pfStorage(const pfStorage &other_)
    : pfIEcontainer(other_)
{
    _data = other_._data;
    _iter = other_._iter;
    return;
}

pfStorage :: ~pfStorage(void)
{
    return;
}

//
// Method: define
//
// Description:
//      Initializes a variable named 'name_' into a map. After calling
//      this method the name is assosiated with corresponding type and
//      can't be used as a variable of some other type. If the name
//      is already reserved, the pfNameAlreadyDefinedException is
//      thrown.
//

// does this data.insert work?

void pfStorage :: defineBitString(const string name_)
{
    checkName2(name_);
    setData(name_, DataContainer::createBitString());
    return;
}

void pfStorage :: defineBoolean(const string name_)
{
    checkName2(name_);
    setData(name_, DataContainer::createBoolean());
    return;
}

void pfStorage :: defineFrame(const string name_)
{
    checkName2(name_);
    setData(name_, DataContainer::createFrame());
    return;
}

void pfStorage :: defineHexString(const string name_)
{
    checkName2(name_);
    setData(name_, DataContainer::createHexString());
    return;
}

void pfStorage :: defineInteger(const string name_)
{
    checkName2(name_);
    setData(name_, DataContainer::createInteger());
    return;
}

void pfStorage :: defineString(const string name_)
{
    checkName2(name_);
    setData(name_, DataContainer::createString());
    return;
}

void pfStorage :: defineStorage(const string name_)
{
    checkName2(name_);
    setData(name_, DataContainer::createStorage());
    return;
}

/*
void pfStorage :: defineIE(const string name_)
{
    checkName2(name_);
    setData(name_, DataContainer::createIE());
    return;
}

void pfStorage :: defineMultiIE(const string name_)
{
    checkMultiNameAndType(name_, IETYPE);
    DataContainer container = DataContainer::createIE();
    container.setMultivariable();
    setData(name_, container);
    return;
}
*/

//
// Method: undefine
//
// Description:
//      Undefines variable
//

void pfStorage :: undefine(const string name_)
{
    _data.erase(name_);
    return;
}

//
// Method: set
//
// Description:
//      Sets a new value. If name_ is not defined, the
//      pfNameUndefinedException is thrown. 
//

void pfStorage :: setBitString(const string name_, pfBitString &value_)
{
    getDataRef(name_).setBitString(value_);
    return;
}

void pfStorage :: setBooleanFalse(const string name_)
{
    getDataRef(name_).setBooleanFalse();
    return;
}

void pfStorage :: setBooleanTrue(const string name_)
{
    getDataRef(name_).setBooleanTrue();
    return;
}

void pfStorage :: setFrame(const string name_, pfFrame &value_)
{
    getDataRef(name_).setFrame(value_);
    return;
}

void pfStorage :: setHexString(const string name_, pfFrame &value_)
{
    getDataRef(name_).setHexString(value_);
    return;
}

void pfStorage :: setInteger(const string name_, pfUlong value_)
{
    getDataRef(name_).setInteger(value_);
    return;
}

void pfStorage :: setString(const string name_, const string &value_)
{
    getDataRef(name_).setString(value_);
    return;
}

void pfStorage :: setStorage(const string name_, pfStorage &value_)
{
    getDataRef(name_).setStorage(value_);
    return;
}

/*
void pfStorage :: setIE(const string name_, pfIE *value_)
{
    getDataRef(name_).setIE(value_);
    return;
}

void pfStorage :: setIE(const string name_, auto_ptr<pfIE> &value_)
{
    getDataRef(name_).setIE(value_);
    return;
}
*/



//
// Method: Get
//
// Description:
//      Gets a current value of the data member. If the given
//      variable (name_) is not defined, the
//      pfNameUndefinedException is thrown.
//

pfBitString pfStorage :: getBitString(const string name_) const
{
    pfBitString result = getDataRef(name_).getBitString();
    return result;
}

bool pfStorage :: getBoolean(const string name_) const
{
    bool result = getDataRef(name_).getBoolean();
    return result;
}

pfFrame pfStorage :: getFrame(const string name_) const
{
    pfFrame result = getDataRef(name_).getFrame();
    return result;
}

pfFrame pfStorage :: getHexString(const string name_) const
{
    pfFrame result = getDataRef(name_).getHexString();
    return result;
}

pfUlong pfStorage :: getInteger(const string name_) const
{
    pfUlong result = getDataRef(name_).getInteger();
    return result;
}

string pfStorage :: getString(const string name_) const
{
    string result = getDataRef(name_).getString();
    return result;
}

pfStorage &pfStorage :: getStorage(const string name_) const
{
    pfStorage &result = getDataRef(name_).getStorage();
    return result;
}

/*
pfIE *pfStorage :: getIE(const string name_) const
{
    pfIE *result = getDataRef(name_).getIE();
    return result;
}
*/

//
// Method: adoptIE
//
// Description:
//       Removes ptr to the IE from the storage thus the called
//       adopts the deleting responsibility over received IE.
//

/*
pfIE *pfStorage :: adoptIE(const string name_)
{
    pfIE *result = getDataRef(name_).adoptIE();
    undefine(name_);
    return result;
}
*/


//
// Method: isValuePresent
//
// Description:
//      Returns non-zero if the variable named name_ has a
//      value. If the variable is not defined, returns false.
//

bool pfStorage :: isValuePresent(const string name_) const
{
    bool result = 0;
    if (isVariableDefined(name_) != 0)
    {
        result = getDataRef(name_).isValuePresent();
    }
    return result;
}

//
// Method: isVariableDefined
//
// Description:
//      Returns non-zero if a variable named 'name_' is defined.
//

bool pfStorage :: isVariableDefined(const string name_) const
{
    mapConstIterType iter = _data.find(name_);
    bool result = (iter != _data.end());
    return result;
}

//
// Method: isAvailable
//
// Description:
//      Returns non-zero if the given variable is defined and
//      has a value.
//

bool pfStorage :: isAvailable(const string name_) const
{
    bool result = 0;
    mapConstIterType iter = _data.find(name_);
    if (iter != _data.end())
    {
        result = (*iter).second.isValuePresent();
    }
    return result;   
}

//
// Method: numberOfVariables
//
// Description:
//      Returns the number of variable instances of name name_
//

pfUlong pfStorage :: numberOfVariables(const string name_) const
{
    pfUlong result = _data.count(name_);
    return result;
}

//
// Method: operator=
//
// Description:
//

pfStorage &pfStorage :: operator=(const pfStorage &other_)
{
    fetch(other_);
    return *this;
}

//
// Method: fetch
//
// Description:
//      Replaces current storage values with values on the
//      other storage.
//

void pfStorage :: fetch(const pfStorage &other_)
{
    if (this != &other_)
    {
        _data = other_._data;
    }
    return;
}

//
// Method: fetchValues
//
// Description:
//      Gets values to current storage from given storage if available.
//

void pfStorage :: fetchValues(const pfStorage &other_)
{
    if (this != &other_)
    {
        mapConstIterType sourceIter;
        mapIterType destIter = _data.begin();

        while(destIter != _data.end())
        {
            string variable = (*destIter).first;
            sourceIter = other_._data.find(variable);
            // Check existence and type
            if ((sourceIter != other_._data.end()) &&
                ((*sourceIter).second.getType() ==
                 (*destIter).second.getType()))
            {
                pfUlong noofSourceVars = other_._data.count(variable);
                pfUlong noofDestVars = _data.count(variable);
                pfUlong noofVariables = (noofSourceVars < noofDestVars) ?
                    noofSourceVars : noofDestVars;
                noofDestVars -= noofVariables; // Variables to skip
                
                while (noofVariables > 0)
                {            
                    (*destIter).second = (*sourceIter).second;
                    sourceIter++;
                    destIter++;
                    noofVariables--;
                }
                // Skip rest
                for (;noofDestVars > 0; noofDestVars--)
                {
                    destIter++;
                }
            }
            else
            {
                destIter++;
            }
        }    
    }
    return;
}

//
// Method: extend
//
// Description:
//      Extends storage with those variables found at given storage but
//      not included in current one.
//

void pfStorage :: extend(const pfStorage &other_)
{
    mapConstIterType sourceIter;
    mapIterType destIter;

    sourceIter = other_._data.begin();
    while (sourceIter != other_._data.end())
    {
        // Check that there is no variable of current name
        // and insert if not found, except if multivariable
        string variable = (*sourceIter).first;
        destIter = _data.find(variable);

        // Copy if destination does not contain variable, or it is
        // multivariable in both of containers.
        if ((destIter == _data.end()) ||
            (((*destIter).second.isMultivariable() != 0) &&
             ((*sourceIter).second.isMultivariable() != 0)))
        {
            // Multimap may contain several variables of the same name.
            // Insert 'em all!
            pfUlong noofVariables;
            for (noofVariables = other_._data.count(variable);
                 noofVariables > 0;
                 noofVariables--)
            {
                (void)_data.insert(*sourceIter); 
                sourceIter++;
            }
        }
        else
        {
            sourceIter++;
        }
    }
    return;
}

//
// Method: copyVariable
//
// Description:
//      Copies one variable (even if multi).
//

void pfStorage :: copyVariable(const pfStorage &other_, const string variable_)
{
    const DataContainer &source = other_.getDataRef(variable_);

    mapIterType iter = _data.find(variable_);
    if (iter != _data.end())
    {
        // If variable already exists, we have to erase it if both
        // source AND destination variables are not multivariables.
        if ((source.isMultivariable() == 0) ||
            ((*iter).second.isMultivariable() == 0))
        {
            // Both are not multivariables
            _data.erase(variable_);
        }
    }
    setData(variable_, source);
    return;
}

//
// Method: copyIfAvailable
//
// Description:
//      Copies a variable if it is available (exists and has value)
//

void pfStorage :: copyIfAvailable(const pfStorage &other_, const string variable_)
{
    if (other_.isAvailable(variable_) != 0)
    {
        copyVariable(other_, variable_);
    }
    return;
}

void pfStorage :: copyIfPresent(const pfStorage &other_, const string variable_)
{
    if (other_.isVariableDefined(variable_) != 0)
    {
        copyVariable(other_, variable_);
    }
    return;
}

//
// Method: operator[]
//
// Description:
//      Returns a reference to a storage typed variable
//

pfStorage &pfStorage :: operator[](const string name_)
{
    pfStorage &result = getDataRef(name_).getStorage();
    return result;
}


pfStorage &pfStorage :: operator[](const string name_) const
{
    pfStorage &result = getDataRef(name_).getStorage();
    return result;
}


// Iterator interface, see Gamma page 257
void pfStorage :: first(void)
{
    _iter = _data.begin();
    _iterUpper = _data.end();
    return;
}

void pfStorage :: first(const string name_)
{
    _iter = _data.find(name_);
    _iterUpper = _data.upper_bound(name_);
    return;
}

void pfStorage :: next(void)
{
    ++_iter;
    return;
}

bool pfStorage :: isDone(void) const
{
    bool result;
    result = (_iter == _iterUpper) ? 1 : 0;
    return result;
}

string pfStorage :: currentItem(void) const
{
    string result; 
    result = (*_iter).first;
    return result; 
}

/*
pfIE *pfStorage :: getCurrentIE(void) const
{
    pfIE &result = (*_iter).second.getIE();
    return result;
}

void pfStorage :: setCurrentIE(pfIE &ie_)
{
    (*_iter).second.setIE(ie_);
    return;
}
*/


//
// Method: getType
//
// Description:
//      Returns the type of the data
//

pfStorage::pfStorageDataType 
pfStorage :: getType(const string name_) const
{
    pfStorageDataType result;
    result = getDataRef(name_).getType();
    return result;
}

//
// Method: print
//
// Description:
//      Prints some information concerning the contents of the storage. 
// 

void pfStorage :: print(void) const
{
    pfFrame frame;
    pfBitString bitString;
    mapConstIterType iter = _data.begin();
    debugUser("Contents of a property");
    while (iter != _data.end())
    {
        debugString("ID", (*iter).first);
        switch((*iter).second.getType())
        {
            case BOOLEANTYPE:
            {
                string trueStr("TRUE");
                string falseStr("FALSE");
                debugUser("TYPE = BOOLEAN");
                debugString("VALUE", (((*iter).second.getBoolean()) ?
                                      trueStr : falseStr));
                break;
            }
            case BITSTRINGTYPE:
                debugUser("TYPE = BIT STRING");
                bitString = (*iter).second.getBitString();
                debugString("VALUE", bitString.toString());
                break;
            case FRAMETYPE:
                debugUser("TYPE = OCTET STRING");
                frame = (*iter).second.getFrame();
                debugFrame("VALUE", frame);
                break;
            case HEXSTRINGTYPE:
                debugUser("TYPE = HEX STRING");
                frame = (*iter).second.getHexString();
                debugFrame("VALUE", frame);
                break;
            case INTTYPE:
                debugUser("TYPE = INTEGER");
                debugPfUlong("VALUE", (*iter).second.getInteger());
                break;
            case STRINGTYPE:
                debugUser("TYPE = CHARACTER STRING");
                debugString("VALUE", (*iter).second.getString());
                break;
            case STORAGETYPE:
                debugUser("TYPE = SEQUENCE");
                (*iter).second.getStorage().print();
                debugUser("END SEQUENCE");
                break;
                /*
            case IETYPE:
                debugUser("TYPE = IE");
                break;
                */
            default: ;
                break;
        }
        ++iter;
    }
    return;
}

//
// Method: checkName
//
// Description:
//      Checks for the name existence and if does not exist
//      throws an error exception.
//

void pfStorage :: checkName(const string name_) const
{
    // If the name_ was not found, iterator is set into an
    // end position.
    mapConstIterType iter = _data.find(name_);
    if (iter == _data.end())
    {
        throw pfNameUndefinedException(name_, PF_EX_INFO);
    }
    return;
}

//
// Method: checkName2
//
// Description:
//      Checks for the name existence and if does exist
//      throws an error exception.
//

void pfStorage :: checkName2(const string name_) const
{
    // The second paramater of pair 'result' tells if the insertion
    // succeed or failed. Failure implicates that the name was already
    // assosiated.
    mapConstIterType iter = _data.find(name_);
    if (iter != _data.end())
    {
        throw pfNameAlreadyDefinedException(name_, PF_EX_INFO);
    }
    return;
}

//
// Method: checkMultiNameAndType
//
// Description:
//      Checks for the name existence and if it does exist and assosiated
//      variable is not multivariable, or found variable is not of given
//      type, throws an error exception.
//

void pfStorage :: checkMultiNameAndType(const string name_,
                                        pfStorageDataType type_) const
{
    mapConstIterType iter = _data.find(name_);
    if ((iter != _data.end()) &&
        (((*iter).second.isMultivariable() == 0) ||
        ((*iter).second.getType() != type_)))
    {
        throw pfNameAlreadyDefinedException(name_, PF_EX_INFO);
    }
    return;
}

pfStorage::DataContainer &pfStorage :: getDataRef(const string name_)
{
    // Name checking could be done with method checkName, but
    // this way we need iterate the map only once.
    mapIterType iter = _data.find(name_);
    if (iter == _data.end())
    {
        throw pfNameUndefinedException(name_, PF_EX_INFO);
    }
    return (*iter).second;
}

const pfStorage::DataContainer &pfStorage :: getDataRef(const string name_) const
{
    // Name checking could be done with method checkName, but
    // this way we need iterate the map only once.
    mapConstIterType iter = _data.find(name_);
    if (iter == _data.end())
    {
        throw pfNameUndefinedException(name_, PF_EX_INFO);
    }
    return (*iter).second;
}

void pfStorage :: setData(const string name_,
                          const pfStorage::DataContainer &datac_)
{
    _iter = _data.insert(mapValueType(name_, datac_));
    return;
}
