#import <Foundation/Foundation.h>

#import "YapDatabase.h"
#import "YapDatabaseConnection.h"
#import "YapDatabaseTransaction.h"

#import "YapDatabaseView.h"
#import "YapDatabaseViewOptions.h"
#import "YapDatabaseViewConnection.h"
#import "YapDatabaseViewTransaction.h"
#import "YapDatabaseViewState.h"

#import "YapDatabaseExtensionPrivate.h"

#import "YapMemoryTable.h"

#import "sqlite3.h"

@class YapCache;
@class YapCollectionKey;

/**
 * This version number is stored in the yap2 table.
 * If there is a major re-write to this class, then the version number will be incremented,
 * and the class can automatically rebuild the tables as needed.
**/
#define YAP_DATABASE_VIEW_CLASS_VERSION 3

static NSString *const changeset_key_state             = @"state";
static NSString *const changeset_key_dirtyMaps         = @"dirtyMaps";
static NSString *const changeset_key_dirtyPages        = @"dirtyPages";
static NSString *const changeset_key_reset             = @"reset";

static NSString *const changeset_key_groupingBlock     = @"groupingBlock";
static NSString *const changeset_key_groupingBlockType = @"groupingBlockType";
static NSString *const changeset_key_sortingBlock      = @"sortingBlock";
static NSString *const changeset_key_sortingBlockType  = @"sortingBlockType";
static NSString *const changeset_key_versionTag        = @"versionTag";

static NSString *const changeset_key_changes           = @"changes";

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

@interface YapDatabaseViewGrouping ()

+ (instancetype)withBlock:(YapDatabaseViewGroupingBlock)block blockType:(YapDatabaseViewBlockType)blockType;

@end

@interface YapDatabaseViewSorting ()

+ (instancetype)withBlock:(YapDatabaseViewSortingBlock)block blockType:(YapDatabaseViewBlockType)blockType;

@end

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

@interface YapDatabaseView () {
@protected
	
	YapDatabaseViewState *latestState;
	
	YapDatabaseViewGroupingBlock groupingBlock;
	YapDatabaseViewSortingBlock sortingBlock;
	
	YapDatabaseViewBlockType groupingBlockType;
	YapDatabaseViewBlockType sortingBlockType;
	
	NSString *versionTag;
	
@public
	
	YapDatabaseViewOptions *options;
}

- (NSString *)mapTableName;
- (NSString *)pageTableName;
- (NSString *)pageMetadataTableName;

- (BOOL)getState:(YapDatabaseViewState **)statePtr
   forConnection:(YapDatabaseViewConnection *)viewConnection;

@end

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

@interface YapDatabaseViewConnection () {
@protected
	
	id sharedKeySetForInternalChangeset;
	id sharedKeySetForExternalChangeset;
	
	YapDatabaseViewGroupingBlock groupingBlock;
	YapDatabaseViewSortingBlock sortingBlock;
	
	YapDatabaseViewBlockType groupingBlockType;
	YapDatabaseViewBlockType sortingBlockType;
	
	NSString *versionTag;
	
	BOOL groupingBlockChanged;
	BOOL sortingBlockChanged;
	BOOL versionTagChanged;
	
@public
	
	__strong YapDatabaseView *view;
	__unsafe_unretained YapDatabaseConnection *databaseConnection;
	
	YapDatabaseViewState *state;
	
	YapCache *mapCache;
	YapCache *pageCache;
	
	NSMutableDictionary *dirtyMaps;
	NSMutableDictionary *dirtyPages;
	NSMutableDictionary *dirtyLinks;
	BOOL reset;
	
	BOOL lastInsertWasAtFirstIndex;
	BOOL lastInsertWasAtLastIndex;
	
	NSMutableArray *changes;
	NSMutableSet *mutatedGroups;
}

- (id)initWithView:(YapDatabaseView *)view databaseConnection:(YapDatabaseConnection *)dbc;
- (void)_flushStatements;

- (BOOL)isPersistentView;

- (void)prepareForReadWriteTransaction;
- (void)postRollbackCleanup;
- (void)postCommitCleanup;

- (NSArray *)internalChangesetKeys;
- (NSArray *)externalChangesetKeys;

- (void)prepareStatement:(sqlite3_stmt **)statement withString:(NSString *)stmtString caller:(SEL)caller_cmd;

- (sqlite3_stmt *)mapTable_getPageKeyForRowidStatement;
- (sqlite3_stmt *)mapTable_setPageKeyForRowidStatement;
- (sqlite3_stmt *)mapTable_removeForRowidStatement;
- (sqlite3_stmt *)mapTable_removeAllStatement;

- (sqlite3_stmt *)pageTable_getDataForPageKeyStatement;
- (sqlite3_stmt *)pageTable_insertForPageKeyStatement;
- (sqlite3_stmt *)pageTable_updateAllForPageKeyStatement;
- (sqlite3_stmt *)pageTable_updatePageForPageKeyStatement;
- (sqlite3_stmt *)pageTable_updateLinkForPageKeyStatement;
- (sqlite3_stmt *)pageTable_removeForPageKeyStatement;
- (sqlite3_stmt *)pageTable_removeAllStatement;

- (void)setGroupingBlock:(YapDatabaseViewGroupingBlock)newGroupingBlock
       groupingBlockType:(YapDatabaseViewBlockType)newGroupingBlockType
            sortingBlock:(YapDatabaseViewSortingBlock)newSortingBlock
        sortingBlockType:(YapDatabaseViewBlockType)newSortingBlockType
              versionTag:(NSString *)newVersionTag;

- (void)getGroupingBlock:(YapDatabaseViewGroupingBlock *)groupingBlockPtr
       groupingBlockType:(YapDatabaseViewBlockType *)groupingBlockTypePtr
            sortingBlock:(YapDatabaseViewSortingBlock *)sortingBlockPtr
        sortingBlockType:(YapDatabaseViewBlockType *)sortingBlockTypePtr;

- (void)getGroupingBlock:(YapDatabaseViewGroupingBlock *)groupingBlockPtr
       groupingBlockType:(YapDatabaseViewBlockType *)groupingBlockTypePtr;

- (void)getSortingBlock:(YapDatabaseViewSortingBlock *)sortingBlockPtr
       sortingBlockType:(YapDatabaseViewBlockType *)sortingBlockTypePtr;

- (void)getGroupingBlockType:(YapDatabaseViewBlockType *)groupingBlockTypePtr
            sortingBlockType:(YapDatabaseViewBlockType *)sortingBlockTypePtr;

@end

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

@interface YapDatabaseViewTransaction () {
@private
	
	YapMemoryTableTransaction *mapTableTransaction;
	YapMemoryTableTransaction *pageTableTransaction;
	YapMemoryTableTransaction *pageMetadataTableTransaction;
	
@protected
	
	__unsafe_unretained YapDatabaseViewConnection *viewConnection;
	__unsafe_unretained YapDatabaseReadTransaction *databaseTransaction;
	
	NSString *lastHandledGroup;
	
	BOOL isRepopulate;
}

- (id)initWithViewConnection:(YapDatabaseViewConnection *)viewConnection
         databaseTransaction:(YapDatabaseReadTransaction *)databaseTransaction;

// The following are declared for view subclasses (such as YapDatabaseFilteredView)

- (void)dropTablesForOldClassVersion:(int)oldClassVersion;
- (BOOL)createTables;

- (NSString *)registeredName;
- (BOOL)isPersistentView;

- (NSString *)pageKeyForRowid:(int64_t)rowid;
- (NSUInteger)indexForRowid:(int64_t)rowid inGroup:(NSString *)group withPageKey:(NSString *)pageKey;
- (BOOL)getRowid:(int64_t *)rowidPtr atIndex:(NSUInteger)index inGroup:(NSString *)group;

- (void)insertRowid:(int64_t)rowid collectionKey:(YapCollectionKey *)collectionKey inNewGroup:(NSString *)group;
- (void)insertRowid:(int64_t)rowid collectionKey:(YapCollectionKey *)collectionKey
                                         inGroup:(NSString *)group
                                         atIndex:(NSUInteger)index
                             withExistingPageKey:(NSString *)existingPageKey;

- (void)insertRowid:(int64_t)rowid
      collectionKey:(YapCollectionKey *)collectionKey
			 object:(id)object
           metadata:(id)metadata
            inGroup:(NSString *)group
        withChanges:(YapDatabaseViewChangesBitMask)flags
              isNew:(BOOL)isGuaranteedNew;

- (void)removeRowid:(int64_t)rowid
      collectionKey:(YapCollectionKey *)collectionKey
            atIndex:(NSUInteger)index
            inGroup:(NSString *)group;

- (void)removeRowid:(int64_t)rowid collectionKey:(YapCollectionKey *)collectionKey;
- (void)removeAllRowidsInGroup:(NSString *)group;
- (void)removeAllRowids;

- (void)enumerateRowidsInGroup:(NSString *)group
                    usingBlock:(void (^)(int64_t rowid, NSUInteger index, BOOL *stop))block;
- (void)enumerateRowidsInGroup:(NSString *)group
                   withOptions:(NSEnumerationOptions)inOptions
                    usingBlock:(void (^)(int64_t rowid, NSUInteger index, BOOL *stop))block;
- (void)enumerateRowidsInGroup:(NSString *)group
                   withOptions:(NSEnumerationOptions)inOptions
                         range:(NSRange)range
                    usingBlock:(void (^)(int64_t rowid, NSUInteger index, BOOL *stop))block;

- (BOOL)containsRowid:(int64_t)rowid;

@end

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

enum {
	YDB_GroupingBlockChanged  = 1 << 0,
	YDB_SortingBlockChanged   = 1 << 1,
	YDB_FilteringBlockChanged = 1 << 2
};

@protocol YapDatabaseViewDependency <NSObject>
@optional

- (void)view:(NSString *)registeredName didRepopulateWithFlags:(int)flags;

@end
