//-< CURSOR.H >------------------------------------------------------*--------*
// GigaBASE                  Version 1.0         (c) 1999  GARRET    *     ?  *
// (Post Relational Database Management System)                      *   /\|  *
//                                                                   *  /  \  *
//                          Created:     20-Nov-98    K.A. Knizhnik  * / [] \ *
//                          Last update: 10-Dec-98    K.A. Knizhnik  * GARRET *
//-------------------------------------------------------------------*--------*
// Table cursor
//-------------------------------------------------------------------*--------*

#ifndef __CURSOR_H__
#define __CURSOR_H__

BEGIN_GIGABASE_NAMESPACE

class dbOrderByNode;

class GIGABASE_DLL_ENTRY dbSelection {
  public:
    enum { FIRST_SEGMENT_SIZE = 16 };
    class segment {
      public:
	segment* prev;
	segment* next;
	size_t   nRows;
	size_t   maxRows;
	oid_t    rows[FIRST_SEGMENT_SIZE];

	static segment* allocate(size_t nRows, segment* after) {
	    segment* s =
		(segment*)new char[sizeof(segment) + sizeof(oid_t)*(nRows-FIRST_SEGMENT_SIZE)];
	    s->next = after->next;
	    s->prev = after;
	    after->next = after->next->prev = s;
	    s->nRows = 0;
	    s->maxRows = nRows;
	    return s;
	}

	void operator delete(void* p) { 
	    delete[] (char*)p;
	}

	segment() {
	    maxRows = FIRST_SEGMENT_SIZE;
	    next = prev = this;
	    nRows = 0;
	}

	void prune() { 
	    next = prev = this;
	}

	~segment() {
	    prev->next = next;
	    next->prev = prev;
	}
    };
    segment   first;
    segment*  curr;
    size_t    nRows;
    size_t    pos;

    void add(oid_t oid) {
	segment* s = first.prev;
	if (s->nRows == s->maxRows) {
	    s = segment::allocate(s->maxRows*2, s);
	}
	s->rows[s->nRows++] = oid;
	nRows += 1;
    }

    void toArray(oid_t* oids);
    void merge(dbSelection& selection);

    void sort(dbDatabase* db, dbOrderByNode* order);
    static int compare(dbRecord* a, dbRecord* b, dbOrderByNode* order);

    static int __cdecl exactKeyCmp(void const* a, void const* b);

    dbSelection() {
	nRows = 0;
	pos = 0;
	curr = &first;
    }
    void reverse();
    void reset();
};

enum dbCursorType {
    dbCursorViewOnly,
    dbCursorForUpdate
};

class GIGABASE_DLL_ENTRY dbL2List {
  public:
    dbL2List* next;
    dbL2List* prev;

    void link(dbL2List* elem) {
	elem->prev = this;
	elem->next = next;
	next = next->prev = elem;
    }
    void unlink() {
#ifdef __INSURE__
        if (((void*) next == (void*) prev) &&
        ((void*) next == (void*) this)) return;
#endif
	next->prev = prev;
	prev->next = next;
        next = prev = this;
    }
    bool isEmpty() {
	return next == this;
    }
    dbL2List() {
	next = prev = this;
    }
    ~dbL2List() {
	unlink();
    }
};


class GIGABASE_DLL_ENTRY dbAnyCursor : public dbL2List {
    friend class dbDatabase;
    friend class dbHashTable;
    friend class dbBtreePage;
    friend class dbBtreeLeafPage;
    friend class dbSubSql;
    friend class dbStatement;
    friend class dbServer;
    friend class dbAnyContainer;
  public:
    int getNumberOfRecords() { return selection.nRows; }

    void remove();

    bool isEmpty() { return currId == 0; }

    bool isLimitReached() { return selection.nRows >= limit; }

    int select(dbQuery& query, dbCursorType aType, void* paramStruct = NULL) {
	paramBase = paramStruct;
	type = aType;
	reset();
	db->select(this, query);
	paramBase = NULL;
	if (gotoFirst() && prefetch) {
	    fetch();
	}
	return selection.nRows;
    }

    // 
    // Extract OIDs of selected recrods in array
    // If "arr" is not null, then this array is used as destination (it should
    // be at least selection.nRows long)
    // If "arr" is null, then new array is created by  new oid_t[] and returned by this
    // method
    //
    oid_t* toArrayOfOid(oid_t* arr) { 
	if (arr == NULL) { 
	    arr = new oid_t[selection.nRows];
	}
	selection.toArray(arr);
	return arr;
    }

    int select(dbQuery& query, void* paramStruct = NULL) {
	return select(query, defaultType, paramStruct);
    }

    int select(char_t const* condition, dbCursorType aType, void* paramStruct = NULL) {
	dbQuery query(condition);
	return select(query, aType, paramStruct);
    }

    int select(char_t const* condition, void* paramStruct = NULL) {
	return select(condition, defaultType, paramStruct);
    }

    int select(dbCursorType aType) {
	type = aType;
	reset();
	db->select(this);
	if (gotoFirst() && prefetch) {
	    fetch();
	}
	return selection.nRows;
    }

    int select() {
	return select(defaultType);
    }

    int selectByKey(char_t const* key, void const* value);

    int selectByKeyRange(char_t const* key, void const* minValue, void const* maxValue);

    void update() {
	assert(type == dbCursorForUpdate && currId != 0);
	updateInProgress = true;
	db->update(currId, table, record);
	updateInProgress = false;
    }

    void removeAll() {
	assert(db != NULL);
	reset();
	db->deleteTable(table);
    }

    void removeAllSelected();

    void setSelectionLimit(size_t lim) { limit = lim; }

    void unsetSelectionLimit() { limit = dbDefaultSelectionLimit; }

    void setPrefetchMode(bool mode) { prefetch = mode; }

    void reset();

    bool isLast(); 
    bool isFirst(); 

    void freeze();
    void unfreeze();

    bool skip(int n);

  protected:
    dbDatabase*        db;
    dbTableDescriptor* table;
    dbCursorType       type;
    dbCursorType       defaultType;
    dbSelection        selection;
    bool               allRecords;
    oid_t              firstId;
    oid_t              lastId;
    oid_t              currId;
    byte*              record;
    size_t             limit;
    dbGetTie           tie;
    void*              paramBase;

    int4*              bitmap; // bitmap to avoid duplicates
    size_t             bitmapSize;
    bool               eliminateDuplicates;
    bool               prefetch;
    bool               removed; // current record was removed
    bool               updateInProgress;

    void checkForDuplicates() {
	if (!eliminateDuplicates && limit > 1) {
	    eliminateDuplicates = true;
	    size_t size = (db->currIndexSize + 31) / 32;
	    if (size > bitmapSize) {
		delete[] bitmap;
		bitmap = new int4[size];
		bitmapSize = size;
	    }
	    memset(bitmap, 0, size*4);
	}
    }

    bool isMarked(oid_t oid) {
	return bitmap != NULL && (bitmap[oid >> 5] & (1 << (oid & 31))) != 0;
    }

    void mark(oid_t oid) {
	if (bitmap != NULL) {
	    bitmap[oid >> 5] |= 1 << (oid & 31);
	}
    }

    bool add(oid_t oid) {
	if (selection.nRows < limit) {
	    if (eliminateDuplicates) {
		if (bitmap[oid >> 5] & (1 << (oid & 31))) {
		    return true;
		}
		bitmap[oid >> 5] |= 1 << (oid & 31);
	    }
	    selection.add(oid);
	    return selection.nRows < limit;
	}
	return false;
    }

    bool gotoNext();
    bool gotoPrev();
    bool gotoFirst();
    bool gotoLast();

    void setCurrent(dbAnyReference const& ref);

    void fetch() {
	table->columns->fetchRecordFields(record,
					  (byte*)db->getRow(tie, currId));
    }

    dbAnyCursor(dbTableDescriptor& aTable, dbCursorType aType, byte* rec)
    : table(&aTable),type(aType),defaultType(aType),
      allRecords(false),currId(0),record(rec)
    {
	limit = dbDefaultSelectionLimit;
	updateInProgress = false;
	prefetch = true;
	removed = false;
	bitmap = NULL;
	bitmapSize = 0;
	eliminateDuplicates = false;
	db = aTable.db;
	paramBase = NULL;
    }
    ~dbAnyCursor();
};

template<class T>
class dbCursor : public dbAnyCursor {
  protected:
    T record;

  public:
    dbCursor(dbCursorType type = dbCursorViewOnly)
        : dbAnyCursor(T::dbDescriptor, type, (byte*)&record) {}

    dbCursor(dbDatabase* aDb, dbCursorType type = dbCursorViewOnly)
        : dbAnyCursor(T::dbDescriptor, type, (byte*)&record) 
    {
	db = aDb;
	table = db->lookupTable(table);
    }
    T* get() {
	return currId == 0 ? (T*)NULL : &record;
    }
    T* next() {
	if (gotoNext()) {
	    fetch();
	    return &record;
	}
	return NULL;
    }
    T* prev() {
	if (gotoPrev()) {
	    fetch();
	    return &record;
	}
	return NULL;
    }
    T* first() {
	if (gotoFirst()) {
	    fetch();
	    return &record;
	}
	return NULL;
    }
    T* last() {
	if (gotoLast()) {
	    fetch();
	    return &record;
	}
	return NULL;
    }
    T* operator ->() {
	assert(currId != 0);
	return &record;
    }
    T* at(dbReference<T> const& ref) {
	setCurrent(ref);
	return &record;
    }
    dbReference<T> currentId() {
	return dbReference<T>(currId);
    }

    //
    // Method nextAvailable allows to iterate through the records in uniform way even when some records 
    // are removed. For example:
    // if (cursor.select(q) > 0) { 
    //     do { 
    //         if (x) { 
    //             cursor.remove();
    //         } else { 
    //             cursor.update();
    //         }
    //     } while (cursor.nextAvaiable());
    //  }
    //
    T* nextAvailable() { 
	if (!removed) { 
	    return next(); 
	} else { 
	    removed = false;
	    return get();
	}
    }
};

class dbParallelQueryContext {
  public:
    dbDatabase* const      db;
    dbCompiledQuery* const query;
    dbAnyCursor*           cursor;
    oid_t                  firstRow;
    dbTableDescriptor*     table;
    dbSelection            selection[dbMaxParallelSearchThreads];

    void search(int i);

    dbParallelQueryContext(dbDatabase* aDb, dbTableDescriptor* desc,
			   dbCompiledQuery* aQuery, dbAnyCursor* aCursor)
      : db(aDb), query(aQuery), cursor(aCursor), firstRow(desc->firstRow), table(desc) {}
};

END_GIGABASE_NAMESPACE

#endif
