/**********
 * Copyright (c) 2003-2004 Greg Parker.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY GREG PARKER ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 **********/

#include "includes.h"
#include "formutils.h"
#include "recordlist.h"


struct RecordList {
    DmOpenRef db;

    UInt16 formID;
    UInt16 tableID;
    UInt16 scrollbarID;

    RecordListDrawProc draw;

    UInt16 topVisibleIndex;
    UInt16 selectedIndex;
};

static RecordList **recordListList = NULL;
static int recordListCount = 0;
static int recordListAllocated = 0;

static RecordList *RecordListForTable(TablePtr table) RECORDLIST_SEGMENT;
static void AddRecordList(RecordList *rl) RECORDLIST_SEGMENT;
static void RemoveRecordList(RecordList *rl) RECORDLIST_SEGMENT;
static Int16 IndexToRow(RecordList *rl, UInt16 index) RECORDLIST_SEGMENT;
static Int16 TotalRowCount(TablePtr table) RECORDLIST_SEGMENT;
static Int16 VisibleRowCount(TablePtr table) RECORDLIST_SEGMENT;
static void RecordListDrawRecord(void *t, Int16 row, Int16 column, RectanglePtr bounds) RECORDLIST_SEGMENT;

static RecordList *RecordListForTable(TablePtr table)
{
    int i;
    UInt16 tableIndex;
    UInt16 tableID;
    UInt16 formID;

    formID = FrmGetActiveFormID();
    tableIndex = FrmGetObjectIndexFromPtr(FrmGetActiveForm(), table);
    tableID = FrmGetObjectId(FrmGetActiveForm(), tableIndex);

    for (i = 0; i < recordListCount; i++) {
        RecordList *r = recordListList[i];
        if (r->formID == formID  &&  r->tableID == tableID) return r;
    }

    return NULL;
}


static void AddRecordList(RecordList *rl)
{
    if (recordListCount == recordListAllocated) {
        RecordList **newList;
        int i;

        recordListAllocated += recordListAllocated + 1;
        newList = MemPtrNew(recordListAllocated * sizeof(RecordList *));
        for (i = 0; i < recordListCount; i++) {
            newList[i] = recordListList[i];
        }
        if (recordListList) MemPtrFree(recordListList);
        recordListList = newList;
    }

    recordListList[recordListCount++] = rl;
}


static void RemoveRecordList(RecordList *rl)
{
    int i;
    
    for (i = 0; i < recordListCount; i++) {
        if (recordListList[i] == rl) {
            recordListList[i] = recordListList[--recordListCount];
            return;
        }
    }
}


// create and populate
RecordList *RecordListNew(DmOpenRef newDB, UInt16 newFormID, UInt16 newTableID, UInt16 newScrollbarID, RecordListDrawProc newDraw)
{
    RecordList *rl = MemPtrNew(sizeof(RecordList));
    rl->db = newDB;
    rl->formID = newFormID;
    rl->tableID = newTableID;
    rl->scrollbarID = newScrollbarID;
    rl->draw = newDraw;

    // fixme read some preferences and restore last selection and visible
    rl->topVisibleIndex = 0;
    rl->selectedIndex = noRecord;

    AddRecordList(rl);
    return rl;
}


void RecordListFree(RecordList *rl)
{
    // fixme write some preferences here
    RemoveRecordList(rl);
    MemPtrFree(rl);
}


UInt16 RecordListCount(RecordList *rl)
{
    return DmNumRecords(rl->db);
}


static Int16 IndexToRow(RecordList *rl, UInt16 index)
{
    if (index != noRecord  &&  index >= rl->topVisibleIndex  &&  
        index < DmNumRecords(rl->db)) 
    {
        return index - rl->topVisibleIndex;
    } else {
        return -1;
    }
}


static Int16 TotalRowCount(TablePtr table)
{
    return TblGetNumberOfRows(table);
}


static Int16 VisibleRowCount(TablePtr table)
{
    Coord rowHeight;
    RectangleType bounds;
    TblGetBounds(table, &bounds);
    rowHeight = TblGetRowHeight(table, 0);
    return bounds.extent.y / rowHeight;
}


// repopulate and redraw
void RecordListUpdate(RecordList *rl)
{
    TablePtr table;
    int totalRows, row, visibleRows;
    UInt16 index;

    if (FrmGetActiveFormID() != rl->formID) return;

    table = PrvGetObjectByID(rl->tableID);
    totalRows = TotalRowCount(table);
    visibleRows = VisibleRowCount(table);
    index = rl->topVisibleIndex;

    // fixme adjust topVisibleIndex if it's too close to or past the bottom

    for (row = 0; row < visibleRows; row++) {
        MemHandle 
            recordH = DmQueryNextInCategory(rl->db, &index, dmAllCategories);
        if (!recordH) {
            // no record for this row or any further rows
            break;
        } else {
            // install record into row
            // UInt32 uniqueID; 
            // DmRecordInfo(ConnectionDB, index, NULL, &uniqueID, NULL);

            TblSetItemStyle(table, row, 0, customTableItem);

            TblSetRowUsable(table, row, true);
            TblSetRowID(table, row, index);
            // TblSetRowData(table, row, uniqueID);
            TblMarkRowInvalid(table, row);

            index++;
        }
    }

    // mark the rest of the table unusable, if any
    for ( ; row < totalRows; row++) {
        TblSetRowUsable(table, row, false);
        TblSetRowID(table, row, noRecord);
        TblMarkRowInvalid(table, row);
    }

    TblSetCustomDrawProcedure(table, 0, RecordListDrawRecord);
    TblSetColumnUsable(table, 0, true);

    // update scoll bar
    {
        ScrollBarPtr scl = PrvGetObjectByID(rl->scrollbarID);
        Int16 value, min, max, pageSize, count;

        count = DmNumRecords(rl->db);
        min = 0;
        pageSize = visibleRows - 1; // page-scroll overlaps by 1
        if (count > pageSize) {
            max = count - pageSize - 1;
            value = rl->topVisibleIndex;
        } else {
            // everything fits at once with no scrolling
            value = 0;
            max = 0;
            pageSize = 0;
        }
        SclSetScrollBar(scl, value, min, max, pageSize);
    }

    TblDrawTable(table); // draws with NO selection!
    row = IndexToRow(rl, rl->selectedIndex);
    if (row >= 0  &&  row < visibleRows) {
        // fixme use TblGetLastUsableRow?
        TblUnhighlightSelection(table);
        TblSelectItem(table, row, 0);
    }
}


UInt16 RecordListSelectedIndex(RecordList *rl)
{
    return rl->selectedIndex;
}


void RecordListSetSelectedIndex(RecordList *rl, UInt16 index)
{
    rl->selectedIndex = index;
    // fixme scroll to selectedIndex if it's not in view
    RecordListUpdate(rl);
}


void RecordListClearSelection(RecordList *rl)
{
    rl->selectedIndex = noRecord;
    RecordListUpdate(rl);
}


MemHandle RecordListQuerySelectedRecord(RecordList *rl)
{
    UInt16 index = RecordListSelectedIndex(rl);
    return RecordListQueryIndexedRecord(rl, index);
}


MemHandle RecordListQueryIndexedRecord(RecordList *rl, UInt16 index)
{
    if (index == noRecord) return NULL;
    else return DmQueryRecord(rl->db, index);
}


MemHandle RecordListGetIndexedRecord(RecordList *rl, UInt16 index)
{
    if (index == noRecord) return NULL;
    else return DmGetRecord(rl->db, index);
}


// if createWithSize is ZERO, then return the record if found or NULL.
// if createWithSize is NON-ZERO, then return the RESIZED record or a NEW one.
// If a new record is created, the selection is CHANGED to that record.
// The returned record must be released with RecordListReleaseRecord.
MemHandle RecordListGetSelectedRecord(RecordList *rl, UInt32 createWithSize)
{
    UInt16 index = RecordListSelectedIndex(rl);

    if (index != noRecord) {
        if (createWithSize == 0) {
            // Selection exists and no resize requested.
            return DmGetRecord(rl->db, index);
        } else {
            // Selection exists and resize requested.
            return DmResizeRecord(rl->db, index, createWithSize);
        }
    }
    else {
        if (createWithSize == 0) {
            // No selection and no creation requested. 
            return NULL;
        } else {
            // No selection but record creation requested.
            MemHandle recordH;
            index = DmNumRecords(rl->db);
            recordH = DmNewRecord(rl->db, &index, createWithSize);
            if (recordH) {
                // zero it out
                DmSet(MemHandleLock(recordH), 0, createWithSize, 0);
                MemHandleUnlock(recordH);
                RecordListSetSelectedIndex(rl, index);
            }
            return recordH;
        }
    }
}


void RecordListReleaseRecord(RecordList *rl, MemHandle recordH, Boolean dirty)
{
    DmOpenRef db = rl->db;
    UInt16 index = DmSearchRecord(recordH, &db);
    // fixme errors
    DmReleaseRecord(db, index, dirty);
    if (dirty) RecordListUpdate(rl);
}


void RecordListDeleteSelectedRecord(RecordList *rl)
{
    RecordListDeleteIndexedRecord(rl, rl->selectedIndex);
}


void RecordListDeleteIndexedRecord(RecordList *rl, UInt16 index)
{
    if (index != noRecord) {
        DmRemoveRecord(rl->db, index);
        if (index == rl->selectedIndex) {
            rl->selectedIndex = noRecord;
        }
        RecordListUpdate(rl);
    }
}


static void RecordListDrawRecord(void *t, Int16 row, Int16 column, 
                                 RectanglePtr bounds)
{
    TablePtr table = (TablePtr)t;
    UInt16 index;
    MemHandle recordH;

    RecordList *rl = RecordListForTable(table);

    index = TblGetRowID(table, row);
    if (index == noRecord) return;

    recordH = DmQueryRecord(rl->db, index);
    if (recordH) {
        WinPushDrawState();

        rl->draw(MemHandleLock(recordH), index, bounds);

        WinPopDrawState();
        MemHandleUnlock(recordH); 
    }
}


Boolean RecordListHandleEvent(RecordList *rl, EventPtr event)
{
    switch (event->eType) {
    case frmOpenEvent:
        if (event->data.frmOpen.formID == rl->formID) {
            RecordListUpdate(rl);
        }
        break;

    case winEnterEvent:
        if (FrmGetFormId((FormPtr)event->data.winEnter.enterWindow) == rl->formID) {
            // fixme could record dirty indicator
            RecordListUpdate(rl);
        }
        break;

    case tblSelectEvent:
        if (RecordListForTable(event->data.tblSelect.pTable) == rl) {
            RecordListSetSelectedIndex(rl, event->data.tblSelect.row + 
                                       rl->topVisibleIndex);
        }
        break;

    case tblExitEvent:
        if (RecordListForTable(event->data.tblExit.pTable) == rl) {
            RecordListClearSelection(rl);
        }
        break;

    case sclRepeatEvent:
        if (event->data.sclRepeat.scrollBarID == rl->scrollbarID) {
            Int16 delta = (event->data.sclRepeat.newValue - 
                           event->data.sclRepeat.value);
            rl->topVisibleIndex += delta;
            RecordListUpdate(rl);
        }
        break;

    default: 
        break;
    }

    return false;
}
