/*
 * freescope - Free source browser
 * Copyright (C) 2001  Olivier Deme
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */

// FILE: dbinf.cpp

/************/
/* INCLUDES */
/************/

#include <algorithm>

#include "dbinf.h"
#include "trace.h"
#include "defs.h"


/********************/
/* GLOBAL VARIABLES */
/********************/

Add2Db add2db;

// Watch out, there shouldn't be any symbol equals to 0!
symbol_type_t DEFINITION          = 1;
symbol_type_t FUNC_CALLED         = 2;
symbol_type_t FUNC_CALL           = 3;
symbol_type_t ASSIGNMENT          = 4;
symbol_type_t INCLUDE             = 5;
symbol_type_t ANY                 = 6;
char*         GLOBAL_SCOPE        = "`GLOBAL";
size_t        GLOBAL_SCOPE_LENGTH = 7;
    

/**************************/
/* STATIC INITIALIZATIONS */
/**************************/

      DbInf* DbInf::m_instance    = 0;
const size_t DbInf::RECORD_LENGTH = sizeof(symbol_code_t) +
                                    sizeof(symbol_type_t) + 
                                    sizeof(symbol_code_t) + 
                                    sizeof(line_nbr_t);
const size_t DbInf::BUFFER_SIZE   = 500 * DbInf::RECORD_LENGTH;

const DbInf::symbol_code_t DbInf::SYMBOL_NEW_FILE   = 0;
const DbInf::symbol_code_t DbInf::SYMBOL_FREE_RECORD = 1;

 

/***********************/
/* FUNCTION DEFINTIONS */
/***********************/

/*
 * FUNCTION:    DbInf::DbInf
 *
 * DESCRIPTION: Constructor
 *
 * IN:
 * IN-OUT:
 * RETURN CODE:
 */

DbInf::DbInf ()
                : m_db_modified(0),
                  m_do_existing_file(false)
{
    add2db = DbInf::add_entry;
    m_buffer.reserve(BUFFER_SIZE);
}

/*
 * FUNCTION:    DbInf::~DbInf
 *
 * DESCRIPTION: Destructor
 *
 * IN:
 * IN-OUT:
 * RETURN CODE:
 */

DbInf::~DbInf ()
{
    // Close the file
    m_db_file.close();
}

/*
 * FUNCTION:    DbInf::instance
 *
 * DESCRIPTION: Return a pointer to the unique instance of this class.
 *
 * IN:
 * IN-OUT:
 * RETURN CODE: A pointer to the instance
 */

DbInf* DbInf::instance()
{
    if (m_instance == NULL)
        m_instance = new DbInf;

    return m_instance;
}

/*
 * FUNCTION:    DbInf::open
 *
 * DESCRIPTION: This function opens a database file. If the file does not exist,
 *              it is created, otherwise the symbol table and file table are
 *              loaded into the multimap attributes.
 *
 * IN:          filename        The name of the database file to open
 * IN-OUT:
 * RETURN CODE: 
 */

void DbInf::open (const string& filename)
{
    // Open the file
    m_db_file.open(filename.c_str(), ios::in | ios::out | ios::binary);

    if (m_db_file == 0)
    {
        LOG(ALL, ERROR) << "Failed to open database file" << endl;
        return;
    }

    m_symbol_code[GLOBAL_SCOPE]          = SYMBOL_FREE_RECORD + 1;
    m_code_symbol[SYMBOL_FREE_RECORD + 1] = GLOBAL_SCOPE;

    // Check if file is empty
    m_db_file.seekg(0, ios::end);
    if (m_db_file.tellg() == 0)
    {
        m_db_file.seekg(0, ios::beg);
        m_db_modified = true;
        return;         // File newly created
    }

    m_do_existing_file = true;

    // Load symbol table
    if (load_symbols() != 0)
    {
        LOG(ALL, ERROR) << "Failed to load file table" << endl;
        return;
    }

    // Load the file table
    if (load_files() != 0)
    {
        LOG(ALL, ERROR) << "Failed to load file table" << endl;
        return;
    }

    m_db_file.seekg(m_st_lower, ios::beg);
}

/*
 * FUNCTION:    DbInf::load_symbols
 *
 * DESCRIPTION: This function reads the symbol table from the database file.
 * IN:
 * IN-OUT:
 * RETURN CODE: 0 on success. 1 otherwise
 */

int DbInf::load_symbols ()
{
    file_ptr_t              cur;
    vector<Uint8>           buffer(BUFFER_SIZE);
    vector<Uint8>::iterator it;

    // Get symbol table boundaries
    m_db_file.seekg((-4) * (int)sizeof(file_ptr_t), ios::end);
    m_db_file.read(&m_st_lower, sizeof(file_ptr_t));
    m_db_file.read(&m_st_upper, sizeof(file_ptr_t));

    // Go to start of symbol table
    m_db_file.seekg(m_st_lower, ios::beg);
    cur = m_st_lower;

    // Populate symbol table
    while (cur < m_st_upper)
    {
        file_ptr_t              old_cur;

        old_cur = cur;

        // Fill the buffer
        if (m_st_upper - cur <= BUFFER_SIZE)
        {
            m_db_file.read(&buffer[0], m_st_upper - cur);
            buffer.resize(m_st_upper - cur);
            cur += m_st_upper - cur;
        }
        else
        {
            m_db_file.read(&buffer[0], BUFFER_SIZE);
            buffer.resize(BUFFER_SIZE);
            cur += BUFFER_SIZE;
        }

        it  = buffer.begin();

        while (it != buffer.end())
        {
            size_t                 i;
            vector<char>::iterator it2;
            string                 symbol_name;
            symbol_code_t          symbol_code;

            // Find end of symbol
            it2 = (char*)(::find(it, buffer.end(), '\0'));

            if (it2 == (char*)buffer.end())
            {
                cur = cur - (buffer.end() - it);
                m_db_file.seekg(cur, ios::beg);
                break;
            }

            symbol_name.assign((char*)it, it2 - (char*)it);

            // Do we have enough characters in the buffer to process one code?
            if (buffer.end() - (Uint8*)it2 < (int)sizeof(symbol_code_t))
            {
                cur = cur - (buffer.end() - it);
                m_db_file.seekg(cur, ios::beg);
                break;
            }

            it = (Uint8*)it2 + 1;

            symbol_code = 0;

            // Get symbol code
            for (i = 0; i < sizeof(symbol_code); ++i)
            {
                symbol_code |= ((symbol_code_t)*it) << (8 * i);
                ++it;
            }

            m_symbol_code[symbol_name] = symbol_code;
            m_code_symbol[symbol_code] = symbol_name;
        }
    }

    return 0;
}

/*
 * FUNCTION:    DbInf::load_files
 *
 * DESCRIPTION: This function reads the file table from the database file.
 * IN:
 * IN-OUT:
 * RETURN CODE: 0 on success. 1 otherwise
 */

int DbInf::load_files ()
{
    string filename;
    time_t time_stamp;

    // Get symbol table boundaries
    m_db_file.seekg((-2) * (int)sizeof(file_ptr_t), ios::end);
    m_db_file.read(&m_ft_lower , sizeof(file_ptr_t));
    m_db_file.read(&m_ft_upper, sizeof(file_ptr_t));

    // Go to start of symbol table
    m_db_file.seekg(m_ft_lower, ios::beg);

    // Populate symbol table
    while (m_db_file.tellg() <= (streampos)m_ft_upper)
    {
        getline(m_db_file, filename, '\0');
        m_files.push_front(filename);
        m_db_file.read(&time_stamp, sizeof(time_t));
        m_time_stamps[filename] = time_stamp;
    }

    m_files_copy = m_files;
    return 0;
}

/*
 * FUNCTION:    DbInf::add_entry
 *
 * DESCRIPTION: This function adds an entry in the database. It should be
 *              called directly from a parser module.
 *      
 * IN:          type                    The symbol type
 *              symbol                  The symbol name
 *              symbol_length           The length of the symbol
 *              scope                   The scope in which the symbol appears
 *              scope_length            The length of the scope
 *              file                    The file containing the symbol
 *              lineno                  The line nbr where is the symbol
 *      
 * IN-OUT:
 * RETURN CODE: 0 on success. 1 on error
 */

int DbInf::add_entry (const symbol_type_t type,
                      const char*         symbol,
                      const size_t        symbol_length,
                      const char*         scope,
                      const size_t        scope_length,
                      const line_nbr_t    lineno)
{
    static string        tmp_symbol;
    static string        tmp_scope;
    symbol_code_t        symbol_code;
    symbol_code_t        scope_code;
    symbol_code_t        symbol_code_mask;
    symbol_type_t        type_mask;
    line_nbr_t           line_mask;
    size_t               i;
    Uint8                my_byte;

    if (m_instance->m_db_modified == false)
        m_instance->m_db_modified = true;

    // Get the symbol code from the symbol table
    tmp_symbol.erase();
    tmp_symbol.append(symbol, symbol_length);

    if ((symbol_code = m_instance->m_symbol_code[tmp_symbol]) == 0)
    {
        // Symbol is not in symbol table. Add it.
        symbol_code = (symbol_code_t)m_instance->m_symbol_code.size() + 2;
        m_instance->m_symbol_code[tmp_symbol]  = symbol_code;
        m_instance->m_code_symbol[symbol_code] = tmp_symbol;
    }

    // Get the scope code from the symbol table
    tmp_scope.erase();
    tmp_scope.append(scope, scope_length);
    scope_code = m_instance->m_symbol_code[tmp_scope];

    if (m_instance->m_do_existing_file == true)
    {
        // This is a file being updated.

        if (m_instance->m_old_file != m_instance->m_file ||
            m_instance->m_buffer.size() >= BUFFER_SIZE)
        {
            // Flush the buffer if needed
            if (m_instance->m_buffer.size() != 0)
            {
                if (m_instance->m_file != m_instance->m_old_file)
                {
                    string tmp;

                    tmp                = m_instance->m_file;
                    m_instance->m_file = m_instance->m_old_file;
                    m_instance->update_records();
                    m_instance->m_file = tmp;
                }
                else
                    m_instance->update_records();
            }
            m_instance->m_old_file = m_instance->m_file;
        }

        // Add the record in the buffer
        for (i = 0; i < sizeof(symbol_code); ++i)
        {
            symbol_code_mask = 0xFF << (8*i);
            my_byte = (Uint8)((symbol_code & symbol_code_mask) >> (8*i));
            m_instance->m_buffer.push_back(my_byte);
        }
        for (i = 0; i < sizeof(type); ++i)
        {
            type_mask = 0xFF << (8*i);
            my_byte = (Uint8)((type & type_mask) >> (8*i));
            m_instance->m_buffer.push_back(my_byte);
        }
        for (i = 0; i < sizeof(scope_code); ++i)
        {
            symbol_code_mask = 0xFF << (8*i);
            my_byte = (Uint8)((scope_code & symbol_code_mask) >> (8*i));
            m_instance->m_buffer.push_back(my_byte);
        }
        for (i = 0; i < sizeof(lineno); ++i)
        {
            line_mask = 0xFF << (8*i);
            my_byte = (Uint8)((lineno & line_mask) >> (8*i));
            m_instance->m_buffer.push_back(my_byte);
        }
    }
    else
    {
        if (m_instance->m_old_file != m_instance->m_file)
        {
            symbol_code_t null_code = 0;

            // Flush the buffer
            if (m_instance->m_db_file.write(&m_instance->m_buffer[0],
                                        m_instance->m_buffer.size()) == 0)
            {
                abort();
            }
            m_instance->m_buffer.clear();
            m_instance->m_st_lower = m_instance->m_db_file.tellg();

            // Write file name.
            if (m_instance->m_db_file.write(&null_code, sizeof(symbol_code_t))
                == 0)
            {
                abort();
            }
            m_instance->m_db_file << m_instance->m_file.c_str() << '\0';

            m_instance->m_old_file = m_instance->m_file;
        }

        // Add a record in the buffer
        for (i = 0; i < sizeof(symbol_code); ++i)
        {
            symbol_code_mask = 0xFF << (8*i);
            my_byte = (Uint8)((symbol_code & symbol_code_mask) >> (8*i));
            m_instance->m_buffer.push_back(my_byte);
        }
        for (i = 0; i < sizeof(type); ++i)
        {
            type_mask = 0xFF << (8*i);
            my_byte = (Uint8)((type & type_mask) >> (8*i));
            m_instance->m_buffer.push_back(my_byte);
        }
        for (i = 0; i < sizeof(scope_code); ++i)
        {
            symbol_code_mask = 0xFF << (8*i);
            my_byte = (Uint8)((scope_code & symbol_code_mask) >> (8*i));
            m_instance->m_buffer.push_back(my_byte);
        }
        for (i = 0; i < sizeof(lineno); ++i)
        {
            line_mask = 0xFF << (8*i);
            my_byte = (Uint8)((lineno & line_mask) >> (8*i));
            m_instance->m_buffer.push_back(my_byte);
        }

        // Do we need to flush the buffer?
        if (m_instance->m_buffer.size() >= BUFFER_SIZE)
        {
        
            if (m_instance->m_db_file.write(&m_instance->m_buffer[0],
                                        m_instance->m_buffer.size()) == 0)
            {
                abort();
            }
            m_instance->m_buffer.clear();
            m_instance->m_st_lower = m_instance->m_db_file.tellg();
        }
    }

    return 0;
}

/*
 * FUNCTION:    DbInf::find
 *
 * DESCRIPTION: Find a series of matches for a given symbol and a given type
 *
 * IN:          symbol          The symbol
 *              type            The type of the symbol
 * IN-OUT:      result          A queueue object of Entry objects that will
 *                              contain the matches
 * RETURN CODE:
 */

void DbInf::find (const string&       symbol,
                  const symbol_type_t type,
                  list<match_t>&      result)
{
    match_t         match;
    static string   match_file;
    symbol_code_t   symbol_code;
    vector<Uint8>    buffer(BUFFER_SIZE);
    file_ptr_t      cur = 0;

    if (result.empty() == false)
        result.erase(result.begin(), result.end());

    // Get the code associated with the symbol.
    if (m_symbol_code.find(symbol) == m_symbol_code.end())
        return;
    
    symbol_code = m_symbol_code[symbol];
    
    // Iterate the database records.
    m_db_file.seekg(0, ios::beg);

    while(cur < m_st_lower)
    {
        symbol_type_t          type2;
        symbol_code_t          symbol_code2;
        symbol_code_t          scope_code;
        vector<Uint8>::iterator it;

        // Fill the buffer
        if (m_st_lower - cur <= BUFFER_SIZE)
        {
            m_db_file.read(&buffer[0], m_st_lower - cur);
            buffer.resize(m_st_lower - cur);
            cur = m_st_lower;
        }
        else
        {
            m_db_file.read(&buffer[0], BUFFER_SIZE);
            buffer.resize(BUFFER_SIZE);
            cur += BUFFER_SIZE;
        }

        it = buffer.begin();

        while (it != buffer.end())
        {
            size_t i;

            // Do we have enough characters in the buffer to process one record?
            if (buffer.end() - it < (int)RECORD_LENGTH)
            {
                cur = cur - (buffer.end() - it);
                m_db_file.seekg(cur, ios::beg);
                break;
            }

            symbol_code2 = 0;

            for (i = 0; i < sizeof(symbol_code2); ++i)
            {
                symbol_code2 |= ((symbol_code_t)*it) << (8 * i);
                ++it;
            }

            if (symbol_code2 == SYMBOL_NEW_FILE)
            {
                // This is a new file
                vector<char>::iterator it2;

                it2 = (char*)(::find(it, buffer.end(), '\0'));

                if (it2 == (char*)buffer.end())
                {
                    cur = cur - (buffer.end() - it) - sizeof(symbol_code_t);
                    m_db_file.seekg(cur, ios::beg);
                    break;
                }

                match_file.assign((char*)it, it2 - (char*)it);
                it = (Uint8*)it2 + 1;
                continue;
            }

            if ((symbol_code2 != symbol_code && type != FUNC_CALLED) ||
                (symbol_code2 == SYMBOL_FREE_RECORD))
            {
                it += RECORD_LENGTH - sizeof(symbol_code2);
                continue;
            }

            type2 = (symbol_type_t)*it;
            ++it;

            if ((type != type2 && type != ANY && type != FUNC_CALLED) ||
                (type == FUNC_CALLED && type2 != FUNC_CALL))
            {
                it += RECORD_LENGTH - sizeof(symbol_code2) - sizeof(type);
                continue;
            }
            
            // Get scope
            scope_code = 0;
            for (i = 0; i < sizeof(scope_code); ++i)
            {
                scope_code |= ((symbol_code_t)*it) << (8 * i);
                ++it;
            }

            match.scope = m_code_symbol[scope_code];

            if ((type == DEFINITION && match.scope != GLOBAL_SCOPE) ||
                (type == FUNC_CALLED && match.scope != symbol))
            {
                it += RECORD_LENGTH - sizeof(symbol_code2) -
                      sizeof(type) - sizeof(scope_code);
                continue;
            }
            
            if (type == FUNC_CALLED)
                match.scope = m_code_symbol[symbol_code2];

            // GLOBAL_SCOPE has the form "`GLOBAL_SCOPE". Get rid of 1st char
            if (match.scope == GLOBAL_SCOPE)
                match.scope = GLOBAL_SCOPE + 1;
            
            match.file = match_file;

            // Get the line nbr
            match.lineno = 0;
            for (i = 0; i < sizeof(line_nbr_t); ++i)
            {
                match.lineno |= ((line_nbr_t)*it) << (8 * i);
                ++it;
            }
            //match.lineno = *(line_nbr_t*)it;
            //it += sizeof(line_nbr_t);

            // Add an element in the match list
            result.push_back(match);
        }
    } 
}

/*
 * FUNCTION:    DbInf::flush
 *
 * DESCRIPTION: Flush the database file
 *
 * IN:
 * IN-OUT:
 * RETURN CODE:
 */

void DbInf::flush ()
{
    cont_string_symcode_t::iterator it_symbols;
    cont_string_time_t::iterator    it_time_stamps;

    for (list<string>::iterator it = m_files.begin();
         it != m_files.end();
         ++it)
    {
        // m_time_stamps[*it] = 0;
        m_db_modified      = true;
    }

    m_files.erase(m_files.begin(), m_files.end());

    // Return directly if database didn't change since opening
    if (m_db_modified == false)
        return;

    // Do we need to flush the buffer?
    if (m_buffer.size() != 0)
    {
        if (m_do_existing_file == true)
        {
            update_records();
            if (m_db_file.tellg() > (streampos)m_st_lower)
                m_st_lower = m_db_file.tellg();
        }
        else
        {
            m_db_file.write(&m_buffer[0], m_buffer.size());
            if (m_do_existing_file == false)
                m_st_lower = m_db_file.tellg();
            else 
            {
                if (m_db_file.tellg() > (streampos)m_st_lower)
                    m_st_lower = m_db_file.tellg();
            }
        }
    }

    // Flush symbol table, file and time stamp table to the file.
    m_db_file.seekg(m_st_lower, ios::beg);

    /****************/
    /* SYMBOL TABLE */
    /****************/

    for (it_symbols  = m_symbol_code.begin();
         it_symbols != m_symbol_code.end();
         ++it_symbols)
    {
        // Dump symbol name
        m_db_file << it_symbols->first.c_str();
        m_db_file << '\0';
        // Dump symbol code
        m_db_file.write(&m_symbol_code[it_symbols->first],
                        sizeof(symbol_code_t));
    }
    m_st_upper = (file_ptr_t)m_db_file.tellg() - 1;

    /**************/
    /* FILE TABLE */
    /**************/

    m_ft_lower = (file_ptr_t)m_db_file.tellg();

    for (it_time_stamps  = m_time_stamps.begin();
         it_time_stamps != m_time_stamps.end();
         ++it_time_stamps)
    {
        // Dump file name
        m_db_file << it_time_stamps->first.c_str();
        m_db_file << '\0';
        // Dump time stamp
        m_db_file.write(&it_time_stamps->second, sizeof(time_t));
    }
    m_ft_upper = (file_ptr_t)m_db_file.tellg() - 1;

    // Dump table boundaries
    m_db_file.write(&m_st_lower, sizeof(file_ptr_t));
    m_db_file.write(&m_st_upper, sizeof(file_ptr_t));
    m_db_file.write(&m_ft_lower, sizeof(file_ptr_t));
    m_db_file.write(&m_ft_upper, sizeof(file_ptr_t));

    // Flush the file
    m_db_file.flush();
    m_old_file.erase();
    m_do_existing_file = true;
    load_files();
    m_buffer.clear();

    m_files_copy = m_files;
}

/*
 * FUNCTION:    DbInf::get_timestamp
 *
 * DESCRIPTION: This function returns the last modification time stamp 
 *              associated with a file.
 *
 * IN:          file            The file name
 * IN-OUT:
 * RETURN CODE: The time stamp associated with the file. 0 if the file hasn't
 *              been found.
 */

time_t DbInf::get_timestamp (const string& file)
{
    return m_time_stamps[file];
}

/*
 * FUNCTION:    DbInf::add_timestamp
 *
 * DESCRIPTION: This function adds the last modification time stamp 
 *              associated with a file.
 *
 * IN:          file            The file name
 *              timestamp       The time stamp
 * INOUT:
 * RETURN CODE:
 */

void DbInf::add_timestamp (const string& file, const time_t timestamp)
{
    m_time_stamps[file] = timestamp;
}

/*
 * FUNCTION:    DbInf::set_file
 *
 * DESCRIPTION: This function indicates which file is being parsed
 *
 * IN:          file            The file being parsed
 * IN-OUT:
 * RETURN CODE:
 */

void DbInf::set_file (const string& file)
{
    m_file = file;
}

/*
 * FUNCTION:    DbInf::next_completion
 *
 * DESCRIPTION: This function finds the next symbol completing the given
 *              stub.
 *
 * IN:          stub            The stub used for completion.
 * IN-OUT:
 * RETURN CODE: A symbol if completion symbol was found. An enpty
 *              string otherwise.
 */

string DbInf::next_completion (const string& stub,
                                     int&    index,
                                     int&    total)
{
    if ((stub != m_stub) || (m_completions.size() == 0))
    {
        find_completions(stub);
        if (m_completions.size() == 0)
        {
            index = 0;
            total = 0;
            return "";
        }
        m_it_completion = m_completions.begin();
        m_completion_index = 1;
    }

    else
    {
        if (++m_it_completion == m_completions.end())
        {
            m_it_completion = m_completions.begin();
            m_completion_index = 1;
        }
        else
            ++m_completion_index;
    }
    
    index = m_completion_index;
    total = m_completions.size();
    return *m_it_completion;
}

/*
 * FUNCTION:    DbInf::prev_completion
 *
 * DESCRIPTION: This function finds the previous symbol completing the given
 *              stub.
 *
 * IN:          stub            The stub used for completion.
 *              index           The match index
 *              total           The total number of match
 * IN-OUT:
 * RETURN CODE: A symbol if completion symbol was found. An enpty
 *              string otherwise.
 */

string DbInf::prev_completion (const string& stub,
                                     int&    index,
                                     int&    total)
{
    if ((stub != m_stub) || (m_completions.size() == 0))
    {
        find_completions(stub);
        if (m_completions.size() == 0)
        {
            index = 0;
            total = 0;
            return "";
        }
        m_it_completion = m_completions.end();
        --m_it_completion;
        m_completion_index = m_completions.size();
    }

    else
    {
        if (m_it_completion == m_completions.begin())
        {
            m_it_completion = m_completions.end();
            --m_it_completion;
            m_completion_index = m_completions.size();
        }
        else
        {
            --m_it_completion;
            --m_completion_index;
        }
    }
    
    index = m_completion_index;
    total = m_completions.size();
    return *m_it_completion;
}

/*
 * FUNCTION:    DbInf::find_completions
 *
 * DESCRIPTION: Build a list of all completions matching the given stub and
 *              sort this list.
 *
 *              index           The match index
 *              total           The total number of match
 * IN:          stub            The stub used for finding completions.
 * IN-OUT:
 * RETURN CODE:
 */

void DbInf::find_completions (const string& stub)
{
    cont_string_symcode_t::iterator it;

    if (stub != m_stub)
        m_stub = stub;

    if (m_completions.size() != 0)
        m_completions.erase(m_completions.begin(), m_completions.end());

    // Find completions
    for (it  = m_symbol_code.begin(); it != m_symbol_code.end(); ++it)
    {
        if (it->first.size() < stub.size())
            continue;
        
        if (it->first.compare(stub, 0, stub.size()) == 0)
            m_completions.push_back(it->first);
    }

    // Sort completions.
    m_completions.sort();
}

/*
 * FUNCTION:    DbInf::update_records
 *
 * DESCRIPTION: This function flushes all records stored in the buffer in the
 *              existing database. This is done the following way:
 *              1) Write one record at the cuurent location.
 *              2) If the current location is a new file, go to the next place
 *                 in the database that has records associated with the current
 *                 file and execute step 1.
 *              
 *              It is possible that we exceed the old number of records in the
 *              database. In that case, we just write all the records at the
 *              current location.
 *              Exceeding the old number of records occurs when we are further
 *              in the database file than m_st_lower.
 * IN:          
 * IN-OUT:
 * OUT:
 * RETURN CODE:
 */

void DbInf::update_records ()
{
    size_t   i;
    unsigned index = 0;                    

    static string old_file;

    if (m_file != old_file)
    {
        old_file = m_file;
        m_db_file.flush();
        find_free_place();
    }

    if (m_db_file.tellg() >= (streampos)m_st_lower)
    {
        /* Just write all the records at that place */
        m_db_file.write(&m_instance->m_buffer[0], m_instance->m_buffer.size());
        m_buffer.clear();
        m_st_lower = m_db_file.tellg();
        m_db_file.flush();
        return;
    }

    while (index < m_buffer.size())
    {
        if ((file_ptr_t)m_db_file.tellg() < m_st_lower)
        {
            symbol_code_t symbol_code;

            /* Check if we can write here */
            symbol_code = 0;

            for (i = 0; i < sizeof(symbol_code_t); ++i)
            {
                Uint8 c;

                m_db_file.read(&c, 1);
                symbol_code |= ((symbol_code_t)c) << (8 * i);
            }
                 
            m_db_file.seekg((-1) * (int)sizeof(symbol_code_t), ios::cur);

            if (symbol_code == SYMBOL_NEW_FILE)
            {
                find_free_place();
                if ((file_ptr_t)m_db_file.tellg() >= m_st_lower)
                {
                    m_db_file.write(&m_instance->m_buffer[index],
                                    m_instance->m_buffer.size() - index);
                    m_buffer.clear();
                    m_db_file.flush();
                    m_st_lower = m_db_file.tellg();
                    return;
                }
            }

            else if (symbol_code != SYMBOL_FREE_RECORD)
                m_db_file.seekg(RECORD_LENGTH, ios::cur);
        }

        /* Write one record */
        m_db_file.write(&m_instance->m_buffer[index], RECORD_LENGTH);
        index += RECORD_LENGTH;

        if ((file_ptr_t)m_db_file.tellg() > m_st_lower)
            m_st_lower = m_db_file.tellg();
    }

    m_instance->m_buffer.clear();
    m_db_file.clear();
    m_db_file.flush();
}

/*
 * FUNCTION:    find_free_place
 *
 * DESCRIPTION: This function finds a place in the database that is suitable 
 *              for writing one record associated with the current file.
 *
 * IN:
 * IN-OUT:
 * OUT:
 * RETURN CODE:
 * SIDE EFFECT:
 */

void DbInf::find_free_place ()
{
    for(;;)
    {
        symbol_code_t symbol_code;

        // If we exceed the old database size, then this is a valid place.
        if ((file_ptr_t)m_db_file.tellg() >= m_st_lower)
        {
            for (size_t i = 0; i < sizeof(symbol_code_t); ++i)
            {
                if (m_db_file.write(&((Uint8*)&SYMBOL_NEW_FILE)[i], 1) == 0)
                    abort();
            }
            m_db_file << m_file << '\0';
            m_db_file.flush();
            break;
        }

        /* Get this symbol */
        symbol_code = 0;

        for (size_t i = 0; i < sizeof(symbol_code_t); ++i)
        {
            Uint8 c;

            m_db_file.read(&c, 1);
            symbol_code |= ((symbol_code_t)c) << (8 * i);
        }
             
        if (symbol_code == SYMBOL_NEW_FILE)
        {
            string filename;

            /* Get the file name */
            getline(m_db_file, filename, '\0');

            if (filename == m_file)
                break;                 // We found a good place
        }
        else
        {
            // Go to the next record
            m_db_file.seekg(RECORD_LENGTH - sizeof(symbol_code_t), ios::cur);
        }
    }
}

/*
 * FUNCTION:    DbInf::invalidate_records
 *
 * DESCRIPTION: This function invalidates all records associated with the
 *              files passed as argument. This should be called whenever a
 *              set of files is about to be re-scanned following modifications.
 *
 * IN:          files
 * IN-OUT:
 * OUT:
 * RETURN CODE:
 */

void DbInf::invalidate_records (const list<string>& files)
{
    symbol_code_t           code;
    vector<Uint8>           buffer(BUFFER_SIZE);
    vector<Uint8>::iterator it;
    bool                    invalidate  = false;
    file_ptr_t              cur         = 0;
    symbol_code_t           free_record = SYMBOL_FREE_RECORD;
    list<string>            invalid_files;
    list<string>::iterator  it2;

    // Append all files that have been removed from freescope.files
    invalid_files = files;
    invalid_files.insert(invalid_files.end(),
                         m_files_copy.begin(),
                         m_files_copy.end());

    for (it2 = m_files_copy.begin(); it2 != m_files_copy.end(); ++it2)
    {
        m_time_stamps[*it2] = 0;
        m_files.remove(*it2);
    }

    if (invalid_files.size() == 0 && m_files.size() == 0)
        return;

    m_db_file.seekg(0, ios::beg);

    while (cur < m_st_lower)
    {
        bool                    mod;
        file_ptr_t              old_cur;

        old_cur = cur;

        // Fill the buffer
        if (m_st_lower - cur <= BUFFER_SIZE)
        {
            m_db_file.read(&buffer[0], m_st_lower - cur);
            buffer.resize(m_st_lower - cur);
            cur += m_st_lower - cur;
        }
        else
        {
            m_db_file.read(&buffer[0], BUFFER_SIZE);
            buffer.resize(BUFFER_SIZE);
            cur += BUFFER_SIZE;
        }

        it  = buffer.begin();
        mod = false;

        while (it != buffer.end())
        {
            size_t i;

            // Do we have enough characters in the buffer to process one record?
            if (buffer.end() - it < (int)RECORD_LENGTH)
            {
                cur = cur - (buffer.end() - it);
                m_db_file.seekg(cur, ios::beg);
                break;
            }

            code = 0;

            // Get symbol code
            for (i = 0; i < sizeof(code); ++i)
            {
                code |= ((symbol_code_t)*it) << (8 * i);
                ++it;
            }

            if (code == SYMBOL_NEW_FILE)
            {
                string                 tmp_file;
                vector<char>::iterator it2;

                // This is a new file

                it2 = (char*)(::find(it, buffer.end(), '\0'));

                if (it2 == (char*)buffer.end())
                {
                    cur = cur - (buffer.end() - it) - sizeof(symbol_code_t);
                    m_db_file.seekg(cur, ios::beg);
                    break;
                }

                tmp_file.assign((char*)it, it2 - (char*)it);
                it = (Uint8*)it2 + 1;

                // Is this a file in the list of modified files?
                if ((::find(invalid_files.begin(),
                           invalid_files.end(),
                           tmp_file) != invalid_files.end()))
                {
                    invalidate = true;
                }
                else
                {
                    invalidate = false;
                }
            }
            else
            {
                if (invalidate == true)
                {
                    it -= sizeof(code);

                    for (i = 0; i < sizeof(symbol_code_t); ++i)
                    {
                        *it = ((Uint8*)&free_record)[i];
                        ++it;
                    }

                    mod = true;
                }

                it += sizeof(symbol_type_t) + sizeof(symbol_code_t) +
                      sizeof(line_nbr_t);
            }
        } // while iterate buffer

        if (mod == true)
        {
            // The buffer has been modified. Flush it.
            m_db_file.seekg(old_cur, ios::beg);
            m_db_file.write(&buffer[0], cur - old_cur);
            m_db_file.flush();
        }

    } // while

    m_db_file.flush();
    m_db_file.seekg(0, ios::beg);

}

/*
 * FUNCTION:    DbInf::is_file_in_db
 *
 * DESCRIPTION: This function checks if a given file is already part of the
 *              database.
 *
 * IN:          file    The file to check for.
 * IN-OUT:
 * OUT:
 * RETURN CODE: true if file is already in databse. false otherwise.
 * SIDE EFFECT:
 */

bool DbInf::is_file_in_db (string file)
{
    list<string>::iterator it;

    if (m_do_existing_file == false)
        return true;

    it = ::find(m_files_copy.begin(), m_files_copy.end(), file);

    if (it != m_files_copy.end())
    {
        m_files_copy.erase(it);
        return true;
    }
    
    return false;
}

void DbInf::dump ()
{
    m_db_file.seekg(0, ios::beg);

    cout.setf(ios::hex, ios::basefield);
    cout << "SYMBOL TABLE STARTS AT 0x" << m_st_lower << endl << endl;
    cout.setf(ios::dec, ios::basefield);

    while ((file_ptr_t)m_db_file.tellg() < m_st_lower)
    {
        symbol_code_t symbol_code;
        symbol_code_t scope_code;
        symbol_type_t type;
        line_nbr_t    lineno;
        unsigned int  i;

        // Read symbol code
        symbol_code = 0;

        for (i = 0; i < sizeof(symbol_code); ++i)
        {
            Uint8 c;

            m_db_file.read(&c, 1);
            symbol_code |= ((symbol_code_t)c) << (8 * i);
        }

        if (symbol_code == SYMBOL_NEW_FILE)
        {
            string filename;

            /* Get the file name */
            getline(m_db_file, filename, '\0');

            cout << endl << "*** " << filename << endl << endl;
            continue;
        }

        // Get type
        m_db_file.read(&type, sizeof(type));

        // Read scope code
        scope_code = 0;

        for (i = 0; i < sizeof(scope_code); ++i)
        {
            Uint8 c;

            m_db_file.read(&c, 1);
            scope_code |= ((symbol_code_t)c) << (8 * i);
        }

        // Get line number
        lineno = 0;

        for (i = 0; i < sizeof(lineno); ++i)
        {
            Uint8 c;

            m_db_file.read(&c, 1);
            lineno |= ((line_nbr_t)c) << (8 * i);
        }

        cout.setf(ios::hex, ios::basefield);
        cout << "0x" << m_db_file.tellg();
        cout.setf(ios::dec, ios::basefield);
        cout << " -- " << m_code_symbol[symbol_code]
             << "  " << (int)type << "  " << m_code_symbol[scope_code] << "  "
             << lineno << endl;
    }
}


syntax highlighted by Code2HTML, v. 0.9.1