// 
// 
// Copyright (C) 2004 SIPfoundry Inc.
// Licensed by SIPfoundry under the LGPL license.
// 
// Copyright (C) 2004 Pingtel Corp.
// Licensed to SIPfoundry under a Contributor Agreement.
// 
// $$
//////////////////////////////////////////////////////////////////////////////

// SYSTEM INCLUDES



// APPLICATION INCLUDES
#include "os/OsLock.h"
#include "os/OsDateTime.h"
#include "os/OsFS.h"
#include "os/OsSysLog.h"
#include "net/Url.h"
#include "utl/UtlTokenizer.h"
#include "fastdb/fastdb.h"
#include "sipdb/ResultSet.h"
#include "sipdb/SIPDBManager.h"
#include "sipdb/DialByNameRow.h"
#include "sipdb/DialByNameDB.h"
#include "sipdb/PermissionDB.h"
#include "sipdb/CredentialDB.h"

REGISTER( DialByNameRow );

// Static Initializers
DialByNameDB* DialByNameDB::spInstance = NULL;
OsMutex  DialByNameDB::sLockMutex (OsMutex::Q_FIFO);
UtlString DialByNameDB::gNp_identityKey("np_identity");
UtlString DialByNameDB::gNp_contactKey("np_contact");
UtlString DialByNameDB::gNp_digitsKey("np_digits");

// GLOBAL VARS
const char digitmap [] = {
    '2','2','2',        // a,b,c
    '3','3','3',        // d,e,f
    '4','4','4',        // g,h,i
    '5','5','5',        // j,k,l
    '6','6','6',        // m,n,o
    '7','7','7','7',    // p,q,r,s
    '8','8','8',        // t,u,v
    '9','9','9','9'     // w,x,y,z
};

/* ============================ CREATORS ================================== */

DialByNameDB::DialByNameDB( const UtlString& name ) : 
    mDatabaseName( name )
{
    // Access the shared table databse
    SIPDBManager* pSIPDBManager = SIPDBManager::getInstance();
    m_pFastDB = pSIPDBManager->getDatabase(name);

    // If we are the first process to attach
    // then we need to load the DB
    int users = pSIPDBManager->getNumDatabaseProcesses(name);
    if ( users == 1 )
    {
        // Load the file implicitly
        this->load();
    }
}

DialByNameDB::~DialByNameDB()
{
    OsSysLog::add(FAC_DB, PRI_DEBUG, "<><>## DialByNameDB:: DESTRUCTOR");
}

/* ============================ MANIPULATORS ============================== */

void
DialByNameDB::releaseInstance()
{
    OsSysLog::add(FAC_DB, PRI_DEBUG, "<><>## DialByNameDB:: releaseInstance() spInstance=%p", spInstance);

    // Critical Section here
    OsLock lock( sLockMutex );

    // if it exists, delete the object and NULL out the pointer
    if (spInstance != NULL) {

        // unregister this table/process from the IMDB
        SIPDBManager::getInstance()->removeDatabase ( spInstance->mDatabaseName );

        // NULL out the fastDB pointer also
        spInstance->m_pFastDB = NULL;

        delete spInstance;
        spInstance = NULL;
    }
}

OsStatus
DialByNameDB::load() const
{
    // Critical Section here
    OsLock lock( sLockMutex );
    OsStatus result = OS_SUCCESS;

    if ( m_pFastDB != NULL ) 
    {
        // Clean out the existing DB rows before loading
        // a new set from persistent storage
        removeAllRows ();

        // Query all Identities with 'AutoAttendant' permission set
        PermissionDB * pPermissionDB = PermissionDB::getInstance();
        ResultSet permissionsResultSet;
        pPermissionDB->getIdentities ( "AutoAttendant", permissionsResultSet );

        CredentialDB * pCredentialDB = CredentialDB::getInstance();
        ResultSet credentialsResultSet;

        UtlString identity, permission;
        int numAutoAttendees = permissionsResultSet.getSize();
        for (int index = 0; index < numAutoAttendees; index++)
        {
            // get the next identity
            UtlString identityKey("identity");
            UtlHashMap record;
            permissionsResultSet.getIndex( index, record );
            UtlString identity = *((UtlString*)record.findValue(&identityKey));

            Url identityUrl (identity);
            pCredentialDB->
                getAllCredentials (
                    identityUrl,
                    credentialsResultSet );

            // we should only have one credential! we're 
            // only interested in the uri column's display name
            if ( credentialsResultSet.getSize() == 1)
            {
                UtlString uriKey("uri");
                UtlHashMap record;
                credentialsResultSet.getIndex( 0, record );
                UtlString uri = *((UtlString*)record.findValue(&uriKey));

                // must have a display name present before inserting a row
                // @TODO convert to url and get display name
                UtlHashMap nvPairs;
                if (!uri.isNull())
                {
                    // Null Element value create a special 
                    // char string we have key and value so insert
                    UtlString* contactValue = 
                        new UtlString( uri ); 

                    // Memory Leak fixes, make shallow copies of static keys
                    UtlString* contactKey = 
                        new UtlString( gNp_contactKey );

                    nvPairs.insertKeyAndValue ( 
                        contactKey, contactValue );
                }
                // Insert the item row into the IMDB
                insertRow ( nvPairs );
            }
        }

        // Reset the changed flags after a successful load
        SIPDBManager::getInstance()->
            setDatabaseChangedFlag("credential", FALSE);
        SIPDBManager::getInstance()->
            setDatabaseChangedFlag("permission", FALSE);
    } else
    {
        result = OS_FAILED;
    }
    return result;
}

OsStatus
DialByNameDB::store()
{
    // this is a no-op for DialByNameDB as its 
    // backing store is actually the join of the credentials 
    // and permission databases
    if ( m_pFastDB != NULL )
    {
        return OS_SUCCESS;
    } else
    {
        return OS_FAILED;
    }
}

UtlBoolean
DialByNameDB::insertRow ( const UtlHashMap& nvPairs ) const
{
    // Note we do not need the identity object here
    // as it is inferred from the uri
    return insertRow (
        Url( *((UtlString*)nvPairs.findValue(&gNp_contactKey)) ) );
}

UtlBoolean
DialByNameDB::insertRow ( const Url& contact ) const
{
    UtlBoolean result = FALSE;

    if ( m_pFastDB != NULL )
    {
        // Fetch the display name
        UtlString identity, displayName, contactString;
        contact.getIdentity( identity );
        contact.getDisplayName( displayName );
        contact.toString( contactString );

        // Make sure that the contact URL is valid and contains
        // a contactIdentity and a contactDisplayName
        if ( !identity.isNull() && !displayName.isNull() )
        {
            UtlSList dtmfStrings;
            getDigitStrings ( displayName, dtmfStrings );
            if ( !dtmfStrings.isEmpty() )
            {
                // Thread Local Storage
                m_pFastDB->attach();

                // Search for a matching row before deciding to update or insert
                dbCursor< DialByNameRow > cursor(dbCursorForUpdate);

                DialByNameRow row;

                dbQuery query;
                // Primary Key is identity
                query="np_identity=",identity;

                // Purge all existing entries associated with this identity
                if ( cursor.select( query ) > 0 )
                {
                    cursor.removeAllSelected();
                } 

                // insert all dtmf combinations for this user
                unsigned int i;
                for (i=0; i<dtmfStrings.entries(); i++)
                {
                    UtlString* digits = (UtlString*)dtmfStrings.at(i);
                    row.np_contact = contactString;
                    row.np_identity = identity;
                    row.np_digits = digits->data();
                    insert (row);
                }
                // Commit rows to memory - multiprocess workaround
                m_pFastDB->detach(0);
            }
        }
    }
    return result;
}

UtlBoolean 
DialByNameDB::getDigitStrings (
    const UtlString& displayName, 
    UtlSList& rDTMFStrings ) const
{
    UtlString lowerString = displayName;
    lowerString.toLower();
    lowerString = lowerString.strip(UtlString::both, '"');
    UtlTokenizer next( lowerString );
    UtlString token;
    UtlSList names;

    // Parse the Display name into a list of names
    // The algorithm for breaking up the string is as follows
    // if the string has > 2 tokens (forget about , separators)
    // create multiple entries in the IMDB for the all combinations
    // so for example
    // John Peter Smith Jr would result in DTMF entries for
    // PeterSmithJrJohn, SmithJrJohnPeter and JrJohnPeterSmith
    // @JC Added - separator for MIT's Avery-Smith example
   
    while (next.next(token, "\t\n,- ")) 
    {
        names.insert ( new UtlString ( token ) );
    }

    size_t numNames = names.entries();

    if ( numNames > 0 ) 
    {
        UtlString reorderedString;
        unsigned int splitPosition = 1;
        do 
        {
            unsigned int i;
            UtlString firstNames;
            for ( i=0; i<splitPosition; i++ ) 
            {
                firstNames += *(UtlString*)names.at(i);
            }

            UtlString lastNames;
            for ( i = splitPosition; i<numNames; i++) 
            {
                lastNames += *(UtlString*)names.at(i);
            }

            // add the new string
            reorderedString = lastNames + firstNames;

            unsigned int len = reorderedString.length();
            
            // calculate thd DTMF digits for the display name
            // firstly strip all , 's and spaces
            UtlString digitString;
            for ( i = 0; i<len; i++ )
            {
                int offset = (int) ( reorderedString(i) - 'a' );
                // filter out white space and comma separators
                if ( (offset >= 0) && (offset < 26) )
                    digitString += digitmap[ offset ];
            }

            rDTMFStrings.insert ( new UtlString (digitString) );

            splitPosition++;
        } while ( splitPosition<numNames );
    }

    // call the desctuctors on all the temp strings
    names.destroyAll();
    return TRUE;
}

UtlBoolean
DialByNameDB::removeRow ( const Url& contact )
{
    UtlBoolean removed = FALSE;
    UtlString identity;
    contact.getIdentity(identity);

    if ( !identity.isNull() && (m_pFastDB != NULL) )
    {
        // Thread Local Storage
        m_pFastDB->attach();

        dbCursor< DialByNameRow > cursor(dbCursorForUpdate);

        dbQuery query;
        query="np_identity=",identity;
        if ( cursor.select( query ) > 0 )
        {
            cursor.removeAllSelected();
            removed = TRUE;
        }
        // Commit rows to memory - multiprocess workaround
        m_pFastDB->detach(0);
    }
    return removed;
}

void
DialByNameDB::removeAllRows () const
{
    if (m_pFastDB != NULL )
    {
        // Thread Local Storage
        m_pFastDB->attach();

        dbCursor< DialByNameRow > cursor(dbCursorForUpdate);

        if (cursor.select() > 0)
        {
            cursor.removeAllSelected();
        }
        // Commit rows to memory - multiprocess workaround
        m_pFastDB->detach(0);
    }
}

void
DialByNameDB::getAllRows(ResultSet& rResultSet) const
{
    // Clear the results
    rResultSet.destroyAll();

    // Check the TableInfo table to see whether we need to reload
    // the Tables from the Credential/Permission tables
    if ( m_pFastDB != NULL )
    {
        SIPDBManager* pSIPDBManager = SIPDBManager::getInstance();
        if ( pSIPDBManager->getDatabaseChangedFlag( "credential" ) || 
             pSIPDBManager->getDatabaseChangedFlag( "permission" )  )
        {
            // Reload this IMDB and reset the changed flags 
            // in both the credential and permission tables
            this->load();
        }

        // Thread Local Storage
        m_pFastDB->attach();

        dbCursor< DialByNameRow > cursor;
        if ( cursor.select() > 0 )
        {
            do {
                UtlHashMap record;
                UtlString* np_identityValue = 
                    new UtlString ( cursor->np_identity );
                UtlString* np_contactValue = 
                    new UtlString ( cursor->np_contact );
                UtlString* np_digitsValue = 
                    new UtlString ( cursor->np_digits );

                // Memory Leak fixes, make shallow copies of static keys
                UtlString* np_identityKey = new UtlString( gNp_identityKey );
                UtlString* np_contactKey = new UtlString( gNp_contactKey );
                UtlString* np_digitsKey = new UtlString( gNp_digitsKey );

                record.insertKeyAndValue ( 
                    np_identityKey, np_identityValue );
                record.insertKeyAndValue ( 
                    np_contactKey, np_contactValue );
                record.insertKeyAndValue ( 
                    np_digitsKey, np_digitsValue );

                rResultSet.addValue(record);
            } while (cursor.next());
        }
        // Commit rows to memory - multiprocess workaround
        m_pFastDB->detach(0);
    }
}

void
DialByNameDB::getContacts (
    const UtlString& digitString,
    ResultSet& rResultSet ) const
{
    // This should erase the contents of the existing resultset
    rResultSet.destroyAll();

    if ( !digitString.isNull() && (m_pFastDB != NULL) )
    {
        // Check the TableInfo table to see whether we need to reload
        // the Tables from the Credential/Permission tables
        SIPDBManager* pSIPDBManager = SIPDBManager::getInstance();
        if ( pSIPDBManager->getDatabaseChangedFlag( "credential" ) || 
             pSIPDBManager->getDatabaseChangedFlag( "permission" )  )
        {
            // Reload this IMDB and rese the changed flags 
            // in the credential and permission tables
            this->load();
        }

        // Thread Local Storage
        m_pFastDB->attach();

        // Search to see if we have a Credential Row
        dbCursor< DialByNameRow > cursor;

        dbQuery query;
        UtlString queryString = "np_digits like '" + digitString + "%'";
        query = queryString;
        if ( cursor.select(query) > 0 ) {
            do {
                UtlHashMap record;
                UtlString* np_identityValue = 
                    new UtlString ( cursor->np_identity );
                UtlString* np_contactValue = 
                    new UtlString ( cursor->np_contact );
                UtlString* np_digitsValue = 
                    new UtlString ( cursor->np_digits );

                // Memory Leak fixes, make shallow copies of static keys
                UtlString* np_identityKey = new UtlString( gNp_identityKey );
                UtlString* np_contactKey = new UtlString( gNp_contactKey );
                UtlString* np_digitsKey = new UtlString( gNp_digitsKey );

                record.insertKeyAndValue ( 
                    np_identityKey, np_identityValue );
                record.insertKeyAndValue ( 
                    np_contactKey, np_contactValue );
                record.insertKeyAndValue ( 
                    np_digitsKey, np_digitsValue );

                rResultSet.addValue(record);
            } while ( cursor.next() );
        }
        // Commit the rows to memory - multiprocess workaround
        m_pFastDB->detach(0);
    }
}

DialByNameDB*
DialByNameDB::getInstance( const UtlString& name )
{
    // Critical Section here
    OsLock lock( sLockMutex );

    // See if this is the first time through for this process
    // Note that this being null => pgDatabase is also null
    if ( spInstance == NULL )
    {
        // Create the singleton class for clients to use
        spInstance = new DialByNameDB( name );
    }
    return spInstance;
}



syntax highlighted by Code2HTML, v. 0.9.1