//
// Database.cpp
//
// (c)Copyright 2000 The Caney Group, LLC. All Rights Reserved.
//
// Programmed by Steven J. Eschweiler
//
// Compiled with Microsoft Visual C++ 6.0 Professional
// 

#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <direct.h>
#include <ctype.h>
#include <winver.h>
#include "VideoPlayer.h"
#include "IndotekBackgroundTechnology.h"
#include "Database.h"
#include "comcthlp.h"
#include "resource.h"

BOOL gbConstructingResultList,gbConstructingPlaylist;

int GetAppVersion(void);

#define WFW_DELETE(p)  { if(p) { delete (p);     (p)=NULL; } }

char *DatabaseCSV=NULL; // Allocated as needed.
int DatabaseCSVFileSize;
int DatabaseCSVRowCount;
int DatabaseCSVColCount;
#define DATABASECSV_MAX_ROWS 16384
#define DATABASECSV_MAX_COLUMNS 26
// DatabaseCSVLine contains pointers into DatabaseCSV for a 
// particular row and column. Each field in DatabaseCSV
// is converted into a NULL terminated string.
char *DatabaseCSVLine[DATABASECSV_MAX_ROWS][DATABASECSV_MAX_COLUMNS]; 

#define CONFIGTXT_PROJECT_NAME      0
#define CONFIGTXT_VIDEOINFORMATION  1
#define CONFIGTXT_TABS              2
#define CONFIGTXT_TEXT_FORMAT       3
#define CONFIGTXT_AUTHORIZATION     4
char *ConfigTXT=NULL; // Allocated as needed.
int ConfigTXTFileSize;
int ConfigTXTNumLines;
#define CONFIGTXT_MAX_LINES 16
char *ConfigTXTLine[CONFIGTXT_MAX_LINES];

typedef struct __FILTERTAB__ {
    char *pName;
    int DatabaseCSVColIndex;
    char *pUniqueEntryName[DATABASECSV_MAX_ROWS];
    int UniqueEntryNameCount;
    BOOL bUniqueEntryFiltered[DATABASECSV_MAX_ROWS];
    BOOL bAllFiltered; // This is true whether all are checked or none are checked.
} FILTERTAB;
FILTERTAB *FilterTab=NULL;
int FilterTabCount;
BOOL gbTempUniqueEntryFiltered[DATABASECSV_MAX_ROWS];

// This array contains TRUE for each row that is included in the 
// selections list box. This is basically used as an intermediary
// for constructing the ResultList[] array below.
BOOL gbFilterResults[DATABASECSV_MAX_ROWS];

// This is the Result List... It contains indexes into the
// database...
int ResultList[DATABASECSV_MAX_ROWS];
int ResultListCount;
BOOL bResultListCheckState[DATABASECSV_MAX_ROWS];

// This is the playlist... It contains indexes into the
// database...
int Playlist[DATABASECSV_MAX_ROWS];
int PlaylistCount;
BOOL bPlaylistCheckState[DATABASECSV_MAX_ROWS];

// This contains notes, one whole page for each entry
typedef struct __NOTES__ {
    char *Note; // Allocated as needed. Size is strlen(*Note)+1 
    int NoteSize; // Number of elements in Note[], including terminating NULL. We must save this because strlen and sizeof won't work on dynamically allocated arrays using the new keyword
} NOTES;
NOTES Notes[DATABASECSV_MAX_ROWS];
int CurrentMovieIndex=0; // Assume none
// The current video clip info is stored here
#define MAX_EDIT_BOX_LENGTH 32768
char TempNotes[MAX_EDIT_BOX_LENGTH];
char CurrentVideoClipInfoString[MAX_EDIT_BOX_LENGTH];

int
db_InternalGetMovieIndexResultList(void)
{
    HTREEITEM hItem;

    // The tree view control's highlighted item
    // is going to give us the CurrentMovieIndex
    hItem = TreeView_GetSelection(GetDlgItem(hwndDialog,IDC_TREERESULTLIST));
    if (hItem==NULL)
        return 0;

    return ResultList[TreeView_GetSelectionIndex(GetDlgItem(hwndDialog,IDC_TREERESULTLIST),hItem)];
}

int
db_InternalGetMovieIndexPlaylist(void)
{
    HTREEITEM hItem;

    // The tree view control's highlighted item
    // is going to give us the CurrentMovieIndex
    hItem = TreeView_GetSelection(GetDlgItem(hwndDialog,IDC_TREEPLAYLIST));
    if (hItem==NULL)
        return 0;

    return Playlist[TreeView_GetSelectionIndex(GetDlgItem(hwndDialog,IDC_TREEPLAYLIST),hItem)];
}

BOOL
db_Open(char *CDROM_Path)
{
    HANDLE hDatabaseCSVFile,hConfigTXTFile;
    char db_pathname[_MAX_PATH],cfg_pathname[_MAX_PATH];
    int LoopVar,LoopVar2,DatabaseCSVRowIndex;
    BOOL bFieldIsQuoted;
    float fDatabaseCSVAuthorizationCode;

    // Open Database.CSV file or exit with error.
    strcpy(db_pathname,CDROM_Path);
    strcat(db_pathname,"\\database.csv");
    if ( (hDatabaseCSVFile = r3d_FileOpen( db_pathname, R3D_READ)) == INVALID_HANDLE_VALUE)
    {
        return FALSE;
    }

    // We generate an authorization code for comparison to
    // the value in the config.txt file. THEY MUST MATCH!
    fDatabaseCSVAuthorizationCode = (float)r3d_FileSize(hDatabaseCSVFile);
    fDatabaseCSVAuthorizationCode *= 314.0F;

    // Open config.txt file or exit with error.
    strcpy(cfg_pathname,CDROM_Path);
    strcat(cfg_pathname,"\\config.txt");
    if ( (hConfigTXTFile = r3d_FileOpen( cfg_pathname, R3D_READ)) == INVALID_HANDLE_VALUE)
    {
        // Close DatabaseCSV file.
        r3d_FileClose(hDatabaseCSVFile);
        return FALSE;
    }

    // Read the ConfigTXT file into memory and close file.
    ConfigTXTFileSize = (int)r3d_FileSize(hConfigTXTFile);
    ConfigTXT = new char[ConfigTXTFileSize+1];
    if ( NULL == ConfigTXT )
    {
        r3d_FileClose(hConfigTXTFile);
        r3d_FileClose(hDatabaseCSVFile);
        MessageBox(hwndMain,"Can not allocate memory for ConfigTXT","Error",MB_OK);
        return FALSE;
    }
    r3d_FileRead(hConfigTXTFile,ConfigTXT,ConfigTXTFileSize);
    r3d_FileClose(hConfigTXTFile);
    ConfigTXTFileSize+=1;
    ConfigTXT[ConfigTXTFileSize-1]=0x00;

    // Read the DatabaseCSV file into memory and close file.
    DatabaseCSVFileSize = (int)r3d_FileSize(hDatabaseCSVFile);
    DatabaseCSV = new char[DatabaseCSVFileSize+1];
    if ( NULL == DatabaseCSV )
    {
        WFW_DELETE(ConfigTXT);
        r3d_FileClose(hConfigTXTFile);
        r3d_FileClose(hDatabaseCSVFile);
        MessageBox(hwndMain,"Can not allocate memory for DatabaseCSV","Error",MB_OK);
        return FALSE;
    }
    r3d_FileRead(hDatabaseCSVFile,DatabaseCSV,DatabaseCSVFileSize);
    r3d_FileClose(hDatabaseCSVFile);
    DatabaseCSVFileSize+=1;
    DatabaseCSV[DatabaseCSVFileSize-1]=0x00;

    // If a field starts with a quotes, it means that there is
    // either quation marks inside the field or that there are
    // comma's in the field.
    //       
    // A qoutation followed immediatley by a quotation means that
    // there is a single quotation mark. If there is a quotation
    // mark at the beginning and end of the field, it means that 
    // there is a comma in there.
    //
    DatabaseCSVRowCount = 0;
    DatabaseCSVColCount = 0;
    if (DatabaseCSV[0] == '\"')
    {
        bFieldIsQuoted = TRUE;
        LoopVar=1;
        DatabaseCSVLine[DatabaseCSVRowCount][DatabaseCSVColCount] = &DatabaseCSV[1];
    }
    else
    {
        bFieldIsQuoted = FALSE;
        LoopVar=0;
        DatabaseCSVLine[DatabaseCSVRowCount][DatabaseCSVColCount] = &DatabaseCSV[0];
    }
    for (; LoopVar<DatabaseCSVFileSize; LoopVar++)
    {
        // If the current field is quoted, then there is either
        // a " or a comma in the field. A quote is represented
        // as "" in the field. A comma is simply a comma.
        if (bFieldIsQuoted)
        {
            // If we are here, we increase LoopVar until we
            // find the field closing mark ", OR "\r\n
            // 
            // At the same time we convert "" to '' and we
            // treat comma's as literal
            //
            for (LoopVar2=LoopVar; LoopVar2<DatabaseCSVFileSize; LoopVar2++)
            {
                if (DatabaseCSV[LoopVar2]=='\"' &&
                    DatabaseCSV[LoopVar2+1]=='\"' )
                {
                    // Double quotes found.
                    DatabaseCSV[LoopVar2]='\'';
                    LoopVar2++;
                    DatabaseCSV[LoopVar2]='\'';
                }
                else if (DatabaseCSV[LoopVar2]=='\"' &&
                    (DatabaseCSV[LoopVar2+1]==',' ||
                    DatabaseCSV[LoopVar2+1]=='\r' ) )
                {
                    // End of field found.
                    DatabaseCSV[LoopVar2]=0x00;
                    // Let the remainder of the LoopVar loop
                    // handle the comma or end of line case.
                    LoopVar2++;
                    LoopVar=LoopVar2;
                    break;
                }
            }
        }

        // Now we increase LoopVar until we find the next field.
        // At the same time, we convert comma's and \r and \n into
        // a value of 0x00 to get NULL terminated strings.
        //
        if (DatabaseCSV[LoopVar]=='\r')
        {
            DatabaseCSV[LoopVar]=0x00;
        }
        if (DatabaseCSV[LoopVar]=='\n')
        {
            DatabaseCSVRowCount++;
            // We may have reached the end of file.
            if (LoopVar+1==DatabaseCSVFileSize)
            {
                DatabaseCSVColCount++; // Make it accurate
                if (DatabaseCSVColCount>26)
                    MessageBox(hwndMain,"The database contains more than 26 columns - This application will not function correctly!","Error",MB_OK);
                break;
            }
            DatabaseCSVColCount=0;
            DatabaseCSV[LoopVar]=0x00;
            DatabaseCSVLine[DatabaseCSVRowCount][DatabaseCSVColCount] = &DatabaseCSV[LoopVar+1];
            if (DatabaseCSV[LoopVar+1] == '\"')
            {
                bFieldIsQuoted = TRUE;
                LoopVar++;
                DatabaseCSVLine[DatabaseCSVRowCount][DatabaseCSVColCount] = &DatabaseCSV[LoopVar+1];
            }
            else
            {
                bFieldIsQuoted = FALSE;
                DatabaseCSVLine[DatabaseCSVRowCount][DatabaseCSVColCount] = &DatabaseCSV[LoopVar+1];
            }
        }
        if (DatabaseCSV[LoopVar]==',')
        {
            DatabaseCSVColCount++;
            if (DatabaseCSVColCount>26)
                MessageBox(hwndMain,"The database contains more than 26 columns - This application will not function correctly!","Error",MB_OK);
            DatabaseCSV[LoopVar]=0x00;
            if (DatabaseCSV[LoopVar+1] == '\"')
            {
                bFieldIsQuoted = TRUE;
                LoopVar++;
                DatabaseCSVLine[DatabaseCSVRowCount][DatabaseCSVColCount] = &DatabaseCSV[LoopVar+1];
            }
            else
            {
                bFieldIsQuoted = FALSE;
                DatabaseCSVLine[DatabaseCSVRowCount][DatabaseCSVColCount] = &DatabaseCSV[LoopVar+1];
            }
        }
    }

    // At this point, the database could contain an empty line 
    // or lines at the end of the database file... This is very
    // possible. We need to go backwards through each line and
    // if we find no data, we reduce the DatabaseCSVRowCount
    // variable for each one we find until no black lines are 
    // found.
    //
    for (DatabaseCSVRowIndex=DatabaseCSVRowCount-1; DatabaseCSVRowIndex>=0; DatabaseCSVRowIndex--)
    {
        // Determine if this line is blank. Since the database
        // should be set up with NO BLANK FIELDS then we
        // simply need to check the first field in every line
        // to establish whether that line is blank or not.
        //
        // if (strcmp(DatabaseCSVLine[DatabaseCSVRowIndex][0],"")==0)
        if (strlen(DatabaseCSVLine[DatabaseCSVRowIndex][0])==0)
        {
            DatabaseCSVRowCount--;
        }
    }

    // Now we need to parse the contents of the Config.txt file.
    ConfigTXTNumLines=1; 
    ConfigTXTLine[0]=&ConfigTXT[0];
    for (LoopVar=0; LoopVar<ConfigTXTFileSize; LoopVar++)
    {
        if (ConfigTXT[LoopVar]=='\r')
        {
            ConfigTXT[LoopVar]=0x00;
        }
        if (ConfigTXT[LoopVar]=='\n' || LoopVar+1==ConfigTXTFileSize)
        {
            ConfigTXT[LoopVar]=0x00;
            if (LoopVar+1!=ConfigTXTFileSize)
            {
                ConfigTXTLine[ConfigTXTNumLines] = &ConfigTXT[LoopVar+1];
                ConfigTXTNumLines++;
            }
        }
    }

    // At this point, we authorize the file. This helps to avoid
    // theft of the software for use with other databases.
    //
    // To accomplish authorization, we use the 
    // fDatabaseCSVAuthorizationCode value which was calculated
    // above and compare it to the authorization code in
    // the config.txt file.
    //
    // THEY MUST MATCH!
    //
    //
    // NOW, in order to avoid problems between future versions of
    // the Word for Word and older databases (of which
    // do not contain the Authorization Line in config.txt),
    // we must look at the file version number. It must be greater
    // than version 1.1 which is coded as 110.
    //
    if (GetAppVersion()>110)
    {
        if (atof(ConfigTXTLine[CONFIGTXT_AUTHORIZATION]) !=
            fDatabaseCSVAuthorizationCode )
        {
            WFW_DELETE(DatabaseCSV);
            WFW_DELETE(ConfigTXT);
            MessageBox(hwndMain,"The database has not been authorized. This application will now exit.","Error",MB_OK|MB_ICONSTOP);
            return FALSE;
        }
    }

    PlaylistCount=0;

    return TRUE;
}

void
db_SetupInterface(void)
{
    HWND hwndTabControl;
    TCITEM tie;
    int LoopVar;
    RECT rcMainClient;
    int FilterTabIndex,RowIndex,UniqueEntryIndex;
    BOOL bInList;

    // If there is no Config.txt, DatabaseCSV or dialog box,
    // there is an error and we return immediately.
    if (ConfigTXT==NULL || DatabaseCSV==NULL || hwndDialog==NULL)
    {
        MessageBox(hwndMain,"ConfigTXT=NULL or DatabaseCSV=NULL or hwndDialog==NULL","Error",MB_OK);
        return;
    }

    // Get the handle of the Filtering TAB Control.
    hwndTabControl = GetDlgItem(hwndDialog, IDC_TABFILTER);

    // Add the appropriate filter options based on the settings
    // in Config.txt, Line 3 (index 2). Remeber, only A-Z
    // are allowed. Something like AB,AC would be illegal and
    // would not work.
    //
    strlwr(ConfigTXTLine[CONFIGTXT_TABS]);    
    FilterTabCount=(int)strlen(ConfigTXTLine[CONFIGTXT_TABS]);

    // MUST BE LOWER CASE!
    strlwr(ConfigTXTLine[CONFIGTXT_VIDEOINFORMATION]); // Make sure this line is lower case!!!

    // Allocate space for FilterTab
    WFW_DELETE(FilterTab);
    FilterTab = new FILTERTAB[FilterTabCount];
    if ( NULL == FilterTab )
    {
        MessageBox(hwndMain,"Can not allocate FilterTab","Error",MB_OK);
        return;
    }

    // Get tab info for FilterTab
    for (LoopVar=0; LoopVar<FilterTabCount; LoopVar++)
    {
        FilterTab[LoopVar].DatabaseCSVColIndex = (int)(ConfigTXTLine[CONFIGTXT_TABS][LoopVar]-'a');
        tie.mask = TCIF_TEXT | TCIF_IMAGE;
        tie.iImage = 0;
        FilterTab[LoopVar].pName = DatabaseCSVLine[0][FilterTab[LoopVar].DatabaseCSVColIndex];
        tie.pszText = FilterTab[LoopVar].pName;
        TabCtrl_InsertItem(hwndTabControl, LoopVar, &tie);
    }

    // Now we need to resize the "client" of the tab control.
    GetClientRect(hwndMain, &rcMainClient);
    SendMessage(hwndMain,WM_SIZE,(WPARAM)SIZE_RESTORED,MAKELPARAM(rcMainClient.right,rcMainClient.bottom));

    // Now we construct an internal list of what to display 
    // in the treeview control for each filtering option. 
    // We maintain a database of checked/unchecked options.
    //
    for (FilterTabIndex=0; FilterTabIndex<FilterTabCount; FilterTabIndex++)
    {
        // We know that the first entry (db row 1) is unique...
        FilterTab[FilterTabIndex].pUniqueEntryName[0] = DatabaseCSVLine[1][FilterTab[FilterTabIndex].DatabaseCSVColIndex];
        FilterTab[FilterTabIndex].UniqueEntryNameCount = 1;
        FilterTab[FilterTabIndex].bUniqueEntryFiltered[0] = FALSE;
        FilterTab[FilterTabIndex].bAllFiltered = TRUE;

        // Now, we search through entire column for any that 
        // are not already in the list
        for (RowIndex=2; RowIndex<DatabaseCSVRowCount; RowIndex++)
        {
            // Is the name at RowIndex in our list?
            bInList = FALSE; // Assume FALSE
            for (UniqueEntryIndex=0; UniqueEntryIndex<FilterTab[FilterTabIndex].UniqueEntryNameCount; UniqueEntryIndex++)
            {
                if ( strcmp(FilterTab[FilterTabIndex].pUniqueEntryName[UniqueEntryIndex], DatabaseCSVLine[RowIndex][FilterTab[FilterTabIndex].DatabaseCSVColIndex]) == 0)
                {
                    // Yes, the name is in our list, move along to 
                    // check next entry at RowIndex
                    bInList = TRUE;
                    break;
                }
            }
            if (bInList==FALSE)
            {
                // No, the name is NOT in our list, add it
                FilterTab[FilterTabIndex].pUniqueEntryName[FilterTab[FilterTabIndex].UniqueEntryNameCount] = DatabaseCSVLine[RowIndex][FilterTab[FilterTabIndex].DatabaseCSVColIndex];
                // For Testing: FilterTab[FilterTabIndex].bUniqueEntryFiltered[FilterTab[FilterTabIndex].UniqueEntryNameCount] = (BOOL)(FilterTab[FilterTabIndex].UniqueEntryNameCount%2);
                FilterTab[FilterTabIndex].bUniqueEntryFiltered[FilterTab[FilterTabIndex].UniqueEntryNameCount] = FALSE;
                FilterTab[FilterTabIndex].UniqueEntryNameCount++;
            }
        }
    }

    // Set All Selections Available in selection list box
    gbFilterResults[0] = FALSE; // Never available
    for (RowIndex=1; RowIndex<DatabaseCSVRowCount; RowIndex++)
        gbFilterResults[RowIndex] = TRUE;


    // Lastly, initialize the TreeView control
    db_ConstructFilterTree(TabCtrl_GetCurSel(hwndTabControl));

    // Put proper icons on TABS
    db_UpdateTABIcons();

    db_UpdateSelections();

}

void
db_ConstructFilterTree(int FilterTabIndex)
{
    int UniqueEntryIndex;//,MaxStrlen,TheStrlen;
    HWND hwndTreeControl;//,hwndTreeControl;
    TVINSERTSTRUCT tvis;

    // Change mouse cursor to hourglass

    // First delete all tree items, if any
    hwndTreeControl = GetDlgItem(hwndDialog, IDC_TREEFILTER);
    TreeView_DeleteAllItems(hwndTreeControl);

    // Initialize list view item structure
    ZeroMemory(&tvis, sizeof(TVINSERTSTRUCT));
    tvis.hParent = NULL;
    tvis.hInsertAfter = TVI_LAST;
    tvis.item.mask = TVIF_TEXT|TVIF_IMAGE|TVIF_SELECTEDIMAGE;
    tvis.item.iImage = 0;
    tvis.item.iSelectedImage = 0;
    tvis.item.cchTextMax = 256;

    // Now based on what tab's selected, we display treeview...
    //

    // Find what FILTER TAB is active, and construct tree.

    // Cycle through possible checked selections and display them
    //
    for (UniqueEntryIndex=0; UniqueEntryIndex<FilterTab[FilterTabIndex].UniqueEntryNameCount; UniqueEntryIndex++)
    {
        tvis.item.pszText = FilterTab[FilterTabIndex].pUniqueEntryName[UniqueEntryIndex];
        TreeView_InsertItem(hwndTreeControl, &tvis);
    }

    // Now set the check states to reflect our internal array
    TreeView_SetCheckStateAll(hwndTreeControl,FilterTab[FilterTabIndex].bUniqueEntryFiltered);
}

BOOL
db_DetermineTreeChecked(HWND hwndTreeControl, int FilterTabIndex)
{
    int UniqueEntryIndex;
    BOOL bNewStateFound;

    bNewStateFound = FALSE;

    if (FilterTab==NULL || FilterTabCount==0)
        return FALSE;

    // Get potential new check states into temporary array
    TreeView_GetCheckStateAll(hwndTreeControl,gbTempUniqueEntryFiltered);

    // Compare old check states with new check states
    for (UniqueEntryIndex=0; UniqueEntryIndex<FilterTab[FilterTabIndex].UniqueEntryNameCount; UniqueEntryIndex++)
    {
        if (gbTempUniqueEntryFiltered[UniqueEntryIndex] != FilterTab[FilterTabIndex].bUniqueEntryFiltered[UniqueEntryIndex] )
        {
            bNewStateFound = TRUE;
        }
    }

    // Ok, now save new check states
    TreeView_GetCheckStateAll(hwndTreeControl,FilterTab[FilterTabIndex].bUniqueEntryFiltered);

    return bNewStateFound;
}

void
db_UpdateTABIcons(void)
{
    HWND hwndTabControl;
    int FilterTabIndex,UniqueEntryIndex;
    BOOL bFiltering;
    TCITEM tie;
    int UniqueEntryFilteredCount;

    // Now we determine whether the TAB should incdicate filtering
    // with a checked box icon.
    hwndTabControl = GetDlgItem(hwndDialog, IDC_TABFILTER);
    for (FilterTabIndex=0; FilterTabIndex<FilterTabCount; FilterTabIndex++)
    {
        FilterTab[FilterTabIndex].bAllFiltered = FALSE; // Assume FALSE
        UniqueEntryFilteredCount=0;
        bFiltering = FALSE;
        for (UniqueEntryIndex=0; UniqueEntryIndex<FilterTab[FilterTabIndex].UniqueEntryNameCount; UniqueEntryIndex++)
        {
            if ( TRUE == FilterTab[FilterTabIndex].bUniqueEntryFiltered[UniqueEntryIndex] )
            {
                UniqueEntryFilteredCount++;
                bFiltering=TRUE;
            }
        }
        if (bFiltering==TRUE)
        {
            if (UniqueEntryFilteredCount==FilterTab[FilterTabIndex].UniqueEntryNameCount)
                FilterTab[FilterTabIndex].bAllFiltered = TRUE;
            // Set Checked Icon On TAB
            tie.mask = TCIF_IMAGE;
            tie.iImage = 1;
            TabCtrl_SetItem(hwndTabControl, FilterTabIndex, &tie);
        }
        else
        {
            FilterTab[FilterTabIndex].bAllFiltered = TRUE;
            // Set Non Checked Icon On TAB
            tie.mask = TCIF_IMAGE;
            tie.iImage = 0;
            TabCtrl_SetItem(hwndTabControl, FilterTabIndex, &tie);
        }
    }
}

void
db_UpdateSelections(void)
{
    int FilterTabIndex,UniqueEntryIndex,RowIndex;
    char buffer[4096];

    // Assume TRUE for all videos found
    for (RowIndex=1; RowIndex<DatabaseCSVRowCount; RowIndex++)
    {
        gbFilterResults[RowIndex] = TRUE;
    }

    // Based on what TAB selections are filtered,
    // we determine what to display.
    for (FilterTabIndex=0; FilterTabIndex<FilterTabCount; FilterTabIndex++)
    {
        if (FilterTab[FilterTabIndex].bAllFiltered==FALSE)
        {
            for (UniqueEntryIndex=0; UniqueEntryIndex<FilterTab[FilterTabIndex].UniqueEntryNameCount; UniqueEntryIndex++)
            {
                if ( FALSE == FilterTab[FilterTabIndex].bUniqueEntryFiltered[UniqueEntryIndex] )
                {
                    // Find all rows containing the entry name
                    // at the column and mark 
                    // gbFilterResults[RowIndex] as FALSE
                    for (RowIndex=1; RowIndex<DatabaseCSVRowCount; RowIndex++)
                    {
                        if (gbFilterResults[RowIndex]==FALSE)
                            continue; // Already marked as not available.
                        if ( strcmp( DatabaseCSVLine[RowIndex][FilterTab[FilterTabIndex].DatabaseCSVColIndex], FilterTab[FilterTabIndex].pUniqueEntryName[UniqueEntryIndex]) == 0)
                        {
                            gbFilterResults[RowIndex] = FALSE;
                        }
                    }
                }
            }
        }
    }

    // Construct ResultList[]
    ResultListCount=0;
    for (RowIndex=1; RowIndex<DatabaseCSVRowCount; RowIndex++)
    {
        if (gbFilterResults[RowIndex] == TRUE)
        {
            ResultList[ResultListCount] = RowIndex;
            ResultListCount++;
        }
    }
    wsprintf(buffer,"Filter Results (%d of %d Found):", ResultListCount,DatabaseCSVRowCount-1);
    SetDlgItemText(hwndDialog,IDC_TEXT_SELECTIONS,buffer);

    // Construct ResultList Control
    db_ConstructResultListControl();
    db_ConstructPlaylistControl(0);
}

void
db_AddToPlaylist(void)
{
    HWND hwndTreeSelections,hwndTreePlaylist;
    int PlaylistIndex;
    BOOL bAlreadyInPlayList;
    int ResultListIndex;

    hwndTreeSelections = GetDlgItem(hwndDialog, IDC_TREERESULTLIST);
    hwndTreePlaylist = GetDlgItem(hwndDialog, IDC_TREEPLAYLIST);    

    // Find out which items are checked in the gbFilterResults
    // TreeView
    TreeView_GetCheckStateAll(hwndTreeSelections,bResultListCheckState);

    // Cycle through Result List, find what's checked and add
    // it to the Playlist provided that it does not already
    // exist in the Platlist.
    for (ResultListIndex=0; ResultListIndex<ResultListCount; ResultListIndex++)
    {
        if (bResultListCheckState[ResultListIndex]==TRUE)
        {
            // Potentially add this to Playlist
            bAlreadyInPlayList = FALSE;
            for (PlaylistIndex=0; PlaylistIndex<PlaylistCount; PlaylistIndex++)
            {
                if (ResultList[ResultListIndex]==Playlist[PlaylistIndex])
                    bAlreadyInPlayList = TRUE;
            }
            if (bAlreadyInPlayList == FALSE)
            {
                Playlist[PlaylistCount] = ResultList[ResultListIndex];
                PlaylistCount++;
            }
        }
    }

    // Now we update the playlist tree view control based
    // on new settings of Playlist[] and PlaylistCount.
    db_ConstructPlaylistControl(0);
    db_ConstructResultListControl(); // Update icons in ResultList
}

void
db_ConstructResultListControl(void)
{
    HWND hwndTreeControl;
    TVINSERTSTRUCT tvis;
    int ResultListIndex,PlaylistIndex;
    char buffer[512];
    BOOL bAlreadyInPlayList;
    HTREEITEM hItem;

    gbConstructingResultList = TRUE;

    hwndTreeControl = GetDlgItem(hwndDialog, IDC_TREERESULTLIST);
    TreeView_DeleteAllItems(hwndTreeControl);
    ZeroMemory(&tvis, sizeof(TVINSERTSTRUCT));
    tvis.hParent = NULL;
    tvis.hInsertAfter = TVI_LAST;
    tvis.item.mask = TVIF_TEXT|TVIF_IMAGE|TVIF_PARAM|TVIF_SELECTEDIMAGE;
    tvis.item.cchTextMax = 256;
    for (ResultListIndex=0; ResultListIndex<ResultListCount; ResultListIndex++)
    {
        // Put up an apropriate icon based on whether the
        // video clip is already in the Playlist
        bAlreadyInPlayList = FALSE;
        for (PlaylistIndex=0; PlaylistIndex<PlaylistCount; PlaylistIndex++)
        {
            if (ResultList[ResultListIndex]==Playlist[PlaylistIndex])
                bAlreadyInPlayList = TRUE;
        }
        if (bAlreadyInPlayList)
        {
            tvis.item.iImage = 0;
            tvis.item.iSelectedImage = 0;
        }
        else
        {
            tvis.item.iImage = 1;
            tvis.item.iSelectedImage = 1;
        }
        tvis.item.lParam = (LPARAM)ResultListIndex;
        wsprintf(buffer,"%s, Disk %s", DatabaseCSVLine[ResultList[ResultListIndex]][(int)(ConfigTXTLine[CONFIGTXT_VIDEOINFORMATION][0]-'a')], DatabaseCSVLine[ResultList[ResultListIndex]][(int)(ConfigTXTLine[CONFIGTXT_VIDEOINFORMATION][2]-'a')] );
        tvis.item.pszText = buffer;
        TreeView_InsertItem(hwndTreeControl, &tvis);
    }

    // Select first item
    hItem = TreeView_GetRoot(hwndTreeControl);
    TreeView_SelectItem(hwndTreeControl, hItem);

    gbConstructingResultList = FALSE;
}

void
db_ConstructPlaylistControl(int SelectionIndex)
{
    HWND hwndTreeControl,hwndControl;
    TVINSERTSTRUCT tvis;
    int ResultListIndex,PlaylistIndex;
    char buffer[512];
    BOOL bPartOfResultList;

    gbConstructingPlaylist = TRUE;

    hwndTreeControl = GetDlgItem(hwndDialog, IDC_TREEPLAYLIST);
    TreeView_DeleteAllItems(hwndTreeControl);
    ZeroMemory(&tvis, sizeof(TVINSERTSTRUCT));
    tvis.hParent = NULL;
    tvis.hInsertAfter = TVI_LAST;
    tvis.item.mask = TVIF_TEXT|TVIF_IMAGE|TVIF_PARAM|TVIF_SELECTEDIMAGE;
    tvis.item.cchTextMax = 256;
    for (PlaylistIndex=0; PlaylistIndex<PlaylistCount; PlaylistIndex++)
    {
        // Put up an apropriate icon based on whether the
        // video clip is part of the ResultList
        bPartOfResultList = FALSE;
        for (ResultListIndex=0; ResultListIndex<ResultListCount; ResultListIndex++)
        {
            if (ResultList[ResultListIndex]==Playlist[PlaylistIndex])
                bPartOfResultList = TRUE;
        }
        if (bPartOfResultList)
        {
            tvis.item.iImage = 0;
            tvis.item.iSelectedImage = 0;
        }
        else
        {
            tvis.item.iImage = 1;
            tvis.item.iSelectedImage = 1;
        }
        tvis.item.lParam = (LPARAM)PlaylistIndex;
        wsprintf(buffer,"%s, Disk %s", DatabaseCSVLine[Playlist[PlaylistIndex]][(int)(ConfigTXTLine[CONFIGTXT_VIDEOINFORMATION][0]-'a')], DatabaseCSVLine[Playlist[PlaylistIndex]][(int)(ConfigTXTLine[CONFIGTXT_VIDEOINFORMATION][2]-'a')] );
        tvis.item.pszText = buffer;
        TreeView_InsertItem(hwndTreeControl, &tvis);
    }

    // Now disable or enable irrelevant controls
    if (PlaylistCount==0)
    {
        hwndControl = GetDlgItem( hwndDialog, IDC_PRESENT );
        EnableWindow(hwndControl, FALSE);
        hwndControl = GetDlgItem( hwndDialog, IDC_MOVEUP );
        EnableWindow(hwndControl, FALSE);
        hwndControl = GetDlgItem( hwndDialog, IDC_MOVEDOWN );
        EnableWindow(hwndControl, FALSE);
        hwndControl = GetDlgItem( hwndDialog, IDC_DELETE );
        EnableWindow(hwndControl, FALSE);
    }
    else
    {
        hwndControl = GetDlgItem( hwndDialog, IDC_PRESENT );
        EnableWindow(hwndControl, TRUE);
        hwndControl = GetDlgItem( hwndDialog, IDC_MOVEUP );
        EnableWindow(hwndControl, TRUE);
        hwndControl = GetDlgItem( hwndDialog, IDC_MOVEDOWN );
        EnableWindow(hwndControl, TRUE);
        hwndControl = GetDlgItem( hwndDialog, IDC_DELETE );
        EnableWindow(hwndControl, TRUE);
    }

    // Select item
    TreeView_SelectItem( hwndTreeControl, TreeView_SetSelectionIndex(hwndTreeControl,SelectionIndex) );

    gbConstructingPlaylist = FALSE;
}

void
db_DeleteFromPlaylist(void)
{
    HWND hwndTreeSelections,hwndTreePlaylist;
    int PlaylistIndex,PlaylistIndex2;

    hwndTreeSelections = GetDlgItem(hwndDialog, IDC_TREERESULTLIST);
    hwndTreePlaylist = GetDlgItem(hwndDialog, IDC_TREEPLAYLIST);    

    // Find out which items are checked in the gbFilterResults
    // TreeView
    TreeView_GetCheckStateAll(hwndTreePlaylist,bPlaylistCheckState);

    // Cycle through Playist, find what's checked and add
    // delete it from the list
    for (PlaylistIndex=PlaylistCount-1; PlaylistIndex>=0; PlaylistIndex--)
    {
        if (bPlaylistCheckState[PlaylistIndex]==TRUE)
        {
            // Shrink the array by this one
            for (PlaylistIndex2=PlaylistIndex+1; PlaylistIndex2<PlaylistCount; PlaylistIndex2++)
            {
                Playlist[PlaylistIndex2-1] = Playlist[PlaylistIndex2];
            }
            PlaylistCount--;
        }
    }

    // Now we update the playlist tree view control based
    // on new settings of Playlist[] and PlaylistCount.
    db_ConstructPlaylistControl(0);
    db_ConstructResultListControl(); // Update icons in ResultList
}

void
db_UpdateEditControl(void)
{
    HWND hwndTabControl,hwndEditControl;
    int LoopVar;
    char TmpBuffer[2];

    // Get a handle on the relevant windows
    hwndTabControl = GetDlgItem(hwndDialog, IDC_TABTEXT);
    hwndEditControl = GetDlgItem(hwndDialog, IDC_EDIT);

    // Disable the Edit Box if no video is selected and return
    if (CurrentMovieIndex==0)
    {
        SetDlgItemText(hwndDialog,IDC_EDIT,"");
        // Change control to read only (disabled)
        EnableWindow(hwndEditControl,FALSE);
        return;
    }
    else
    {
        EnableWindow(hwndEditControl,TRUE);
    }

    // Display Edit Box contents based upon the TAB control
    if (TabCtrl_GetCurSel(hwndTabControl)==0)
    {
        // Video Clip Information TAB is selected

        // Change control to read only
        Edit_SetReadOnly(hwndEditControl,TRUE);

        // Construct the Video Clip Information string
        strcpy(CurrentVideoClipInfoString,"");
        for (LoopVar=0; LoopVar<(int)strlen(ConfigTXTLine[CONFIGTXT_TEXT_FORMAT]); LoopVar++)
        {
            if (isalpha(ConfigTXTLine[CONFIGTXT_TEXT_FORMAT][LoopVar]))
            {
                // Add a string
                if (islower(ConfigTXTLine[CONFIGTXT_TEXT_FORMAT][LoopVar]))
                {
                    // Convert letter to index
                    strcat(CurrentVideoClipInfoString, DatabaseCSVLine[ CurrentMovieIndex ][(int)(ConfigTXTLine[CONFIGTXT_TEXT_FORMAT][LoopVar]-'a')]);
                }
                else
                {
                    // Convert letter to index
                    strcat(CurrentVideoClipInfoString, DatabaseCSVLine[0][(int)(ConfigTXTLine[CONFIGTXT_TEXT_FORMAT][LoopVar]-'A')]);
                }
            }
            else if ( ConfigTXTLine[CONFIGTXT_TEXT_FORMAT][LoopVar] =='\\' ||
                ConfigTXTLine[CONFIGTXT_TEXT_FORMAT][LoopVar] =='/' )
            {
                // Add a new line
                strcat(CurrentVideoClipInfoString, "\r\n");
            }
            else
            {
                // Add literal translation
                TmpBuffer[0] = ConfigTXTLine[CONFIGTXT_TEXT_FORMAT][LoopVar];
                TmpBuffer[1] = 0x00;
                strcat(CurrentVideoClipInfoString, TmpBuffer);
            }
        }

        // Display the Video Clip Information string
        SetDlgItemText(hwndDialog,IDC_EDIT,CurrentVideoClipInfoString);
    }
    else
    {
        // Video Clip Notes TAB is selected

        // Change control to read and write
        Edit_SetReadOnly(hwndEditControl,FALSE);

        // Display notes, if any, in edit bos
        if (Notes[CurrentMovieIndex].Note != NULL)
            SetDlgItemText(hwndDialog,IDC_EDIT,Notes[CurrentMovieIndex].Note);
        else
            SetDlgItemText(hwndDialog,IDC_EDIT,"");
    }
}

void
db_SaveCurrentNotes(void)
{
    if (TabCtrl_GetCurSel(GetDlgItem(hwndDialog, IDC_TABTEXT))==0)
        return;

    // Kill curent note item in array
    if (Notes[CurrentMovieIndex].Note != NULL)
    {
        WFW_DELETE(Notes[CurrentMovieIndex].Note);
        Notes[CurrentMovieIndex].NoteSize = 0;
    }

    // Get data from edit box. Add one for NULL.
    Notes[CurrentMovieIndex].NoteSize = Edit_GetTextLength(GetDlgItem(hwndDialog,IDC_EDIT))+1;
    Notes[CurrentMovieIndex].Note = new char[Notes[CurrentMovieIndex].NoteSize];
    if ( NULL == Notes[CurrentMovieIndex].Note )
    {
        MessageBox(hwndMain,"Error:\n\nCan not allocate memory for Video Clip Notes!","Error",MB_OK);
    }
    GetDlgItemText(hwndDialog,IDC_EDIT,Notes[CurrentMovieIndex].Note,Notes[CurrentMovieIndex].NoteSize);
}

void
db_MoveClipUp(void)
{
    HTREEITEM hItem;
    int TempIndex,CurrentPlaylistIndex;

    hItem = TreeView_GetSelection(GetDlgItem(hwndDialog,IDC_TREEPLAYLIST));
    CurrentPlaylistIndex = TreeView_GetSelectionIndex(GetDlgItem(hwndDialog,IDC_TREEPLAYLIST),hItem);

    if (CurrentPlaylistIndex==0)
        return;

    // Simply swap Playlist[CurrentPlaylistIndex-1] with
    // Playlist[CurrentPlaylistIndex] and reconstruct 
    // playlist with newly moved item selected.
    TempIndex = Playlist[CurrentPlaylistIndex];
    Playlist[CurrentPlaylistIndex] = Playlist[CurrentPlaylistIndex-1];
    Playlist[CurrentPlaylistIndex-1] = TempIndex;
    CurrentPlaylistIndex-=1;
    db_ConstructPlaylistControl(CurrentPlaylistIndex);
}

void
db_MoveClipDown(void)
{
    HTREEITEM hItem;
    int TempIndex,CurrentPlaylistIndex;

    hItem = TreeView_GetSelection(GetDlgItem(hwndDialog,IDC_TREEPLAYLIST));
    CurrentPlaylistIndex = TreeView_GetSelectionIndex(GetDlgItem(hwndDialog,IDC_TREEPLAYLIST),hItem);

    if (CurrentPlaylistIndex==PlaylistCount-1)
        return;

    // Simply swap Playlist[CurrentPlaylistIndex-1] with
    // Playlist[CurrentPlaylistIndex] and reconstruct 
    // playlist with newly moved item selected.
    TempIndex = Playlist[CurrentPlaylistIndex];
    Playlist[CurrentPlaylistIndex] = Playlist[CurrentPlaylistIndex+1];
    Playlist[CurrentPlaylistIndex+1] = TempIndex;
    CurrentPlaylistIndex+=1;
    db_ConstructPlaylistControl(CurrentPlaylistIndex);
}

char *
db_GetCurrentMovieFilename(void)
{
    return DatabaseCSVLine[CurrentMovieIndex][(int)(ConfigTXTLine[CONFIGTXT_VIDEOINFORMATION][1]-'a')];
}

int
db_GetCurrentMovieDiskNumber(void)
{
    return atoi(DatabaseCSVLine[CurrentMovieIndex][(int)(ConfigTXTLine[CONFIGTXT_VIDEOINFORMATION][2]-'a')]);
}

char *
db_GetProjectName(void)
{
    return ConfigTXTLine[CONFIGTXT_PROJECT_NAME];
}

int
db_PlaylistCount(void)
{
    return PlaylistCount;
}

char *
db_PlaylistFilename(int PlaylistIndex)
{
    return DatabaseCSVLine[Playlist[PlaylistIndex]][(int)(ConfigTXTLine[CONFIGTXT_VIDEOINFORMATION][1]-'a')];
}

int
db_PlaylistDiskNumber(int PlaylistIndex)
{
    return atoi(DatabaseCSVLine[Playlist[PlaylistIndex]][(int)(ConfigTXTLine[CONFIGTXT_VIDEOINFORMATION][2]-'a')]);
}

void
db_DeletePlaylist(void)
{
    int PlaylistIndex;

    // I don't know how important this stuff is:
    for (PlaylistIndex=0; PlaylistIndex<PlaylistCount; PlaylistIndex++)
    {
        Playlist[PlaylistIndex] = 0;
        bPlaylistCheckState[PlaylistIndex] = FALSE;
    }

    // Set count to zero
    PlaylistCount=0;

    // Now update display
    TreeView_DeleteAllItems(GetDlgItem(hwndDialog, IDC_TREEPLAYLIST));
    EnableWindow(GetDlgItem(hwndDialog,IDC_PRESENT), FALSE);
    EnableWindow(GetDlgItem(hwndDialog,IDC_MOVEUP), FALSE);
    EnableWindow(GetDlgItem(hwndDialog,IDC_MOVEDOWN), FALSE);
    EnableWindow(GetDlgItem(hwndDialog,IDC_DELETE), FALSE);
}

void
db_DeleteNotes(void)
{
    int NoteIndex;

    for (NoteIndex=0; NoteIndex<DATABASECSV_MAX_ROWS; NoteIndex++)
    {
        WFW_DELETE(Notes[NoteIndex].Note);
    }

    // Now update display... Set tab to movie info...
    CurrentMovieIndex = 0;

    // Set to first TAB
    TabCtrl_SetCurSel(GetDlgItem(hwndDialog,IDC_TABTEXT),0);
}

void
db_ClearFilterOptions(void)
{
    int FilterTabIndex,UniqueEntryIndex;
    TCITEM tie;

    // Reset filter options to none
    for (FilterTabIndex=0; FilterTabIndex<FilterTabCount; FilterTabIndex++)
    {
        for (UniqueEntryIndex=0; UniqueEntryIndex<FilterTab[FilterTabIndex].UniqueEntryNameCount; UniqueEntryIndex++)
        {
            FilterTab[FilterTabIndex].bUniqueEntryFiltered[UniqueEntryIndex] = FALSE;
        }
        FilterTab[FilterTabIndex].bAllFiltered = TRUE;

        // Set Non Checked Icon On TAB
        tie.mask = TCIF_IMAGE;
        tie.iImage = 0;
        TabCtrl_SetItem( GetDlgItem(hwndDialog,IDC_TABFILTER), FilterTabIndex, &tie );
    }

    // Uncheck all in current filter tree
    TreeView_SetCheckStateAll( GetDlgItem(hwndDialog,IDC_TREEFILTER), FilterTab[TabCtrl_GetCurSel(GetDlgItem(hwndDialog,IDC_TABFILTER))].bUniqueEntryFiltered );

    // Set to first TAB
    TabCtrl_SetCurSel(GetDlgItem(hwndDialog,IDC_TABFILTER),0);

    // Update the result list
    db_UpdateSelections();

    // Set focus to result list
    SetFocus(GetDlgItem(hwndDialog, IDC_TREERESULTLIST));
}

void
db_ExportFilterSelections(HANDLE hFile)
{
    int FilterTabIndex;

    for (FilterTabIndex=0; FilterTabIndex<FilterTabCount; FilterTabIndex++)
    {
        r3d_FileWrite(hFile,&FilterTab[FilterTabIndex].bUniqueEntryFiltered,sizeof(FilterTab[FilterTabIndex].bUniqueEntryFiltered));
        r3d_FileWrite(hFile,&FilterTab[FilterTabIndex].bAllFiltered,sizeof(FilterTab[FilterTabIndex].bAllFiltered));
    }
}


void
db_ExportPlaylist(HANDLE hFile)
{
    int PlaylistIndex;

    r3d_FileWrite(hFile,&PlaylistCount,sizeof(PlaylistCount));
    for (PlaylistIndex=0; PlaylistIndex<PlaylistCount; PlaylistIndex++)
    {
        r3d_FileWrite(hFile,&Playlist[PlaylistIndex],sizeof(Playlist[PlaylistIndex]));
    }
}

void
db_ExportNotes(HANDLE hFile)
{
    int NoteIndex;

    for (NoteIndex=0; NoteIndex<DATABASECSV_MAX_ROWS; NoteIndex++)
    {
        r3d_FileWrite(hFile,&Notes[NoteIndex].NoteSize,sizeof(Notes[NoteIndex].NoteSize));
        if (Notes[NoteIndex].NoteSize>0)
        {
            strcpy(TempNotes,Notes[NoteIndex].Note);
            r3d_FileWrite(hFile,&TempNotes,Notes[NoteIndex].NoteSize);
        }
    }
}

void
db_ImportFilterSelections(HANDLE hFile)
{
    int FilterTabIndex;

    for (FilterTabIndex=0; FilterTabIndex<FilterTabCount; FilterTabIndex++)
    {
        r3d_FileRead(hFile,&FilterTab[FilterTabIndex].bUniqueEntryFiltered,sizeof(FilterTab[FilterTabIndex].bUniqueEntryFiltered));
        r3d_FileRead(hFile,&FilterTab[FilterTabIndex].bAllFiltered,sizeof(FilterTab[FilterTabIndex].bAllFiltered));
    }
    TreeView_SetCheckStateAll( GetDlgItem(hwndDialog,IDC_TREEFILTER), FilterTab[TabCtrl_GetCurSel(GetDlgItem(hwndDialog,IDC_TABFILTER))].bUniqueEntryFiltered );
}

void
db_ImportPlaylist(HANDLE hFile)
{
    int PlaylistIndex;

    r3d_FileRead(hFile,&PlaylistCount,sizeof(PlaylistCount));
    for (PlaylistIndex=0; PlaylistIndex<PlaylistCount; PlaylistIndex++)
    {
        r3d_FileRead(hFile,&Playlist[PlaylistIndex],sizeof(Playlist[PlaylistIndex]));
    }
    db_ConstructPlaylistControl(0);
}

void
db_ImportNotes(HANDLE hFile)
{
    int NoteIndex;

    for (NoteIndex=0; NoteIndex<DATABASECSV_MAX_ROWS; NoteIndex++)
    {
        WFW_DELETE(Notes[NoteIndex].Note);
        Notes[NoteIndex].NoteSize = 0;

        r3d_FileRead(hFile,&Notes[NoteIndex].NoteSize,sizeof(Notes[NoteIndex].NoteSize));
        if (Notes[NoteIndex].NoteSize>0)
        {
            Notes[NoteIndex].Note = new char[Notes[NoteIndex].NoteSize];
            if ( NULL == Notes[NoteIndex].Note )
            {
                MessageBox(hwndMain,"Error:\n\nCan not allocate memory for Video Clip Notes!","Error",MB_OK);
            }
            r3d_FileRead(hFile,&TempNotes,Notes[NoteIndex].NoteSize);
            strcpy(Notes[NoteIndex].Note,TempNotes);
        }
    }
}

char *
db_GetStartTime(int MovieIndex)
{
    return DatabaseCSVLine[MovieIndex][(int)(ConfigTXTLine[CONFIGTXT_VIDEOINFORMATION][3]-'a')];
}

char *
db_GetEndTime(int MovieIndex)
{
    return DatabaseCSVLine[MovieIndex][(int)(ConfigTXTLine[CONFIGTXT_VIDEOINFORMATION][4]-'a')];
}

float
db_GetFramesPerSecond(int MovieIndex)
{
    return (float)atof(DatabaseCSVLine[MovieIndex][(int)(ConfigTXTLine[CONFIGTXT_VIDEOINFORMATION][5]-'a')]);
}

int
db_GetCurrentMovieIndex(void)
{
    return CurrentMovieIndex;
}

int
db_GetMovieIndexFromPlaylistIndex(int PlaylistIndex)
{
    return Playlist[PlaylistIndex];
}

int
db_SetMovieIndex(BOOL bUsePlaylistIndex)
{
    if (bUsePlaylistIndex)
    {
        if (PlaylistCount==0)
            CurrentMovieIndex = db_InternalGetMovieIndexResultList();
        else
            CurrentMovieIndex = db_InternalGetMovieIndexPlaylist();
    }
    else
    {
        if (ResultListCount==0)
            CurrentMovieIndex = db_InternalGetMovieIndexPlaylist();
        else
            CurrentMovieIndex = db_InternalGetMovieIndexResultList();
    }

    // Update Status Bar and ToolBar states
    if (CurrentMovieIndex==0)
    {
        Status_SetText(hwndStatusBar, 2, 0, " ");
        ToolBar_EnableButton(hwndToolBar, IDB_PLAY_PLAY, FALSE);
        ToolBar_EnableButton(hwndToolBar, IDB_PLAY_PAUSE, FALSE);
        ToolBar_EnableButton(hwndToolBar, IDB_PLAY_STOP, FALSE);
    }
    else
    {
        Status_SetText(hwndStatusBar, 2, 0, db_GetCurrentMovieFilename());
        ToolBar_EnableButton(hwndToolBar, IDB_PLAY_PLAY, TRUE);
        ToolBar_EnableButton(hwndToolBar, IDB_PLAY_PAUSE, TRUE);
        ToolBar_EnableButton(hwndToolBar, IDB_PLAY_STOP, TRUE);
    }

    // Update Edit Box
    db_UpdateEditControl();

    return CurrentMovieIndex;
}

void
db_DeleteAllAllocated(void)
{
    WFW_DELETE(DatabaseCSV);
    WFW_DELETE(ConfigTXT);
    WFW_DELETE(FilterTab);
}

int
GetAppVersion(void)
{
    char szFullPath[_MAX_PATH]; 
    DWORD dwVerHnd;
    DWORD dwVerInfoSize; 
    int VersionNumber=100; // Assume 1.00
    UINT dwBytes = 0;
    LPVOID lpBuffer = 0;
    VS_FIXEDFILEINFO *lpvsFixedFileInfo = {0};
    unsigned char *VersionMemory; 

    // Get version information from the application 
    GetModuleFileName(hInst, szFullPath, sizeof(szFullPath)); 
    dwVerInfoSize = GetFileVersionInfoSize(szFullPath, &dwVerHnd); 
    if (dwVerInfoSize) 
    { 
        // If we were able to get the information, process it: 
        VersionMemory = new unsigned char[dwVerInfoSize];
        if ( NULL == VersionMemory )
        {
            MessageBox(hwndMain,"Can not allocate temporary memory for version information","Error",MB_OK|MB_ICONERROR);
            return 100; // We return 100 for program version 1.0
        }
        GetFileVersionInfo(szFullPath, dwVerHnd, dwVerInfoSize, VersionMemory);

        VerQueryValue(VersionMemory, TEXT("\\"), &lpBuffer, &dwBytes);
        lpvsFixedFileInfo = (VS_FIXEDFILEINFO *)lpBuffer;
        VersionNumber = ((lpvsFixedFileInfo->dwProductVersionMS&0xFFFF0000)>>16);

        WFW_DELETE(VersionMemory);
    }

    return VersionNumber;
}