package bbolt

import (
	
	
	
)

// txPending holds a list of pgids and corresponding allocation txns
// that are pending to be freed.
type txPending struct {
	ids              []pgid
	alloctx          []txid // txids allocating the ids
	lastReleaseBegin txid   // beginning txid of last matching releaseRange
}

// pidSet holds the set of starting pgids which have the same span size
type pidSet map[pgid]struct{}

// freelist represents a list of all pages that are available for allocation.
// It also tracks pages that have been freed but are still in use by open transactions.
type freelist struct {
	freelistType   FreelistType                // freelist type
	ids            []pgid                      // all free and available free page ids.
	allocs         map[pgid]txid               // mapping of txid that allocated a pgid.
	pending        map[txid]*txPending         // mapping of soon-to-be free page ids by tx.
	cache          map[pgid]bool               // fast lookup of all free and pending page ids.
	freemaps       map[uint64]pidSet           // key is the size of continuous pages(span), value is a set which contains the starting pgids of same size
	forwardMap     map[pgid]uint64             // key is start pgid, value is its span size
	backwardMap    map[pgid]uint64             // key is end pgid, value is its span size
	allocate       func(txid txid, n int) pgid // the freelist allocate func
	free_count     func() int                  // the function which gives you free page number
	mergeSpans     func(ids pgids)             // the mergeSpan func
	getFreePageIDs func() []pgid               // get free pgids func
	readIDs        func(pgids []pgid)          // readIDs func reads list of pages and init the freelist
}

// newFreelist returns an empty, initialized freelist.
func newFreelist( FreelistType) *freelist {
	 := &freelist{
		freelistType: ,
		allocs:       make(map[pgid]txid),
		pending:      make(map[txid]*txPending),
		cache:        make(map[pgid]bool),
		freemaps:     make(map[uint64]pidSet),
		forwardMap:   make(map[pgid]uint64),
		backwardMap:  make(map[pgid]uint64),
	}

	if  == FreelistMapType {
		.allocate = .hashmapAllocate
		.free_count = .hashmapFreeCount
		.mergeSpans = .hashmapMergeSpans
		.getFreePageIDs = .hashmapGetFreePageIDs
		.readIDs = .hashmapReadIDs
	} else {
		.allocate = .arrayAllocate
		.free_count = .arrayFreeCount
		.mergeSpans = .arrayMergeSpans
		.getFreePageIDs = .arrayGetFreePageIDs
		.readIDs = .arrayReadIDs
	}

	return 
}

// size returns the size of the page after serialization.
func ( *freelist) () int {
	 := .count()
	if  >= 0xFFFF {
		// The first element will be used to store the count. See freelist.write.
		++
	}
	return int(pageHeaderSize) + (int(unsafe.Sizeof(pgid(0))) * )
}

// count returns count of pages on the freelist
func ( *freelist) () int {
	return .free_count() + .pending_count()
}

// arrayFreeCount returns count of free pages(array version)
func ( *freelist) () int {
	return len(.ids)
}

// pending_count returns count of pending pages
func ( *freelist) () int {
	var  int
	for ,  := range .pending {
		 += len(.ids)
	}
	return 
}

// copyall copies a list of all free ids and all pending ids in one sorted list.
// f.count returns the minimum length required for dst.
func ( *freelist) ( []pgid) {
	 := make(pgids, 0, .pending_count())
	for ,  := range .pending {
		 = append(, .ids...)
	}
	sort.Sort()
	mergepgids(, .getFreePageIDs(), )
}

// arrayAllocate returns the starting page id of a contiguous list of pages of a given size.
// If a contiguous block cannot be found then 0 is returned.
func ( *freelist) ( txid,  int) pgid {
	if len(.ids) == 0 {
		return 0
	}

	var ,  pgid
	for ,  := range .ids {
		if  <= 1 {
			panic(fmt.Sprintf("invalid page allocation: %d", ))
		}

		// Reset initial page if this is not contiguous.
		if  == 0 || - != 1 {
			 = 
		}

		// If we found a contiguous block then remove it and return it.
		if (-)+1 == pgid() {
			// If we're allocating off the beginning then take the fast path
			// and just adjust the existing slice. This will use extra memory
			// temporarily but the append() in free() will realloc the slice
			// as is necessary.
			if ( + 1) ==  {
				.ids = .ids[+1:]
			} else {
				copy(.ids[-+1:], .ids[+1:])
				.ids = .ids[:len(.ids)-]
			}

			// Remove from the free cache.
			for  := pgid(0);  < pgid(); ++ {
				delete(.cache, +)
			}
			.allocs[] = 
			return 
		}

		 = 
	}
	return 0
}

// free releases a page and its overflow for a given transaction id.
// If the page is already free then a panic will occur.
func ( *freelist) ( txid,  *page) {
	if .id <= 1 {
		panic(fmt.Sprintf("cannot free page 0 or 1: %d", .id))
	}

	// Free page and all its overflow pages.
	 := .pending[]
	if  == nil {
		 = &txPending{}
		.pending[] = 
	}
	,  := .allocs[.id]
	if  {
		delete(.allocs, .id)
	} else if (.flags & freelistPageFlag) != 0 {
		// Freelist is always allocated by prior tx.
		 =  - 1
	}

	for  := .id;  <= .id+pgid(.overflow); ++ {
		// Verify that page is not already free.
		if .cache[] {
			panic(fmt.Sprintf("page %d already freed", ))
		}
		// Add to the freelist and cache.
		.ids = append(.ids, )
		.alloctx = append(.alloctx, )
		.cache[] = true
	}
}

// release moves all page ids for a transaction id (or older) to the freelist.
func ( *freelist) ( txid) {
	 := make(pgids, 0)
	for ,  := range .pending {
		if  <=  {
			// Move transaction's pending pages to the available freelist.
			// Don't remove from the cache since the page is still free.
			 = append(, .ids...)
			delete(.pending, )
		}
	}
	.mergeSpans()
}

// releaseRange moves pending pages allocated within an extent [begin,end] to the free list.
func ( *freelist) (,  txid) {
	if  >  {
		return
	}
	var  pgids
	for ,  := range .pending {
		if  <  ||  >  {
			continue
		}
		// Don't recompute freed pages if ranges haven't updated.
		if .lastReleaseBegin ==  {
			continue
		}
		for  := 0;  < len(.ids); ++ {
			if  := .alloctx[];  <  ||  >  {
				continue
			}
			 = append(, .ids[])
			.ids[] = .ids[len(.ids)-1]
			.ids = .ids[:len(.ids)-1]
			.alloctx[] = .alloctx[len(.alloctx)-1]
			.alloctx = .alloctx[:len(.alloctx)-1]
			--
		}
		.lastReleaseBegin = 
		if len(.ids) == 0 {
			delete(.pending, )
		}
	}
	.mergeSpans()
}

// rollback removes the pages from a given pending tx.
func ( *freelist) ( txid) {
	// Remove page ids from cache.
	 := .pending[]
	if  == nil {
		return
	}
	var  pgids
	for ,  := range .ids {
		delete(.cache, )
		 := .alloctx[]
		if  == 0 {
			continue
		}
		if  !=  {
			// Pending free aborted; restore page back to alloc list.
			.allocs[] = 
		} else {
			// Freed page was allocated by this txn; OK to throw away.
			 = append(, )
		}
	}
	// Remove pages from pending list and mark as free if allocated by txid.
	delete(.pending, )
	.mergeSpans()
}

// freed returns whether a given page is in the free list.
func ( *freelist) ( pgid) bool {
	return .cache[]
}

// read initializes the freelist from a freelist page.
func ( *freelist) ( *page) {
	if (.flags & freelistPageFlag) == 0 {
		panic(fmt.Sprintf("invalid freelist page: %d, page type is %s", .id, .typ()))
	}
	// If the page.count is at the max uint16 value (64k) then it's considered
	// an overflow and the size of the freelist is stored as the first element.
	var ,  = 0, int(.count)
	if  == 0xFFFF {
		 = 1
		 := *(*pgid)(unsafeAdd(unsafe.Pointer(), unsafe.Sizeof(*)))
		 = int()
		if  < 0 {
			panic(fmt.Sprintf("leading element count %d overflows int", ))
		}
	}

	// Copy the list of page ids from the freelist.
	if  == 0 {
		.ids = nil
	} else {
		var  []pgid
		 := unsafeIndex(unsafe.Pointer(), unsafe.Sizeof(*), unsafe.Sizeof([0]), )
		unsafeSlice(unsafe.Pointer(&), , )

		// copy the ids, so we don't modify on the freelist page directly
		 := make([]pgid, )
		copy(, )
		// Make sure they're sorted.
		sort.Sort(pgids())

		.readIDs()
	}
}

// arrayReadIDs initializes the freelist from a given list of ids.
func ( *freelist) ( []pgid) {
	.ids = 
	.reindex()
}

func ( *freelist) () []pgid {
	return .ids
}

// write writes the page ids onto a freelist page. All free and pending ids are
// saved to disk since in the event of a program crash, all pending ids will
// become free.
func ( *freelist) ( *page) error {
	// Combine the old free pgids and pgids waiting on an open transaction.

	// Update the header flag.
	.flags |= freelistPageFlag

	// The page.count can only hold up to 64k elements so if we overflow that
	// number then we handle it by putting the size in the first element.
	 := .count()
	if  == 0 {
		.count = uint16()
	} else if  < 0xFFFF {
		.count = uint16()
		var  []pgid
		 := unsafeAdd(unsafe.Pointer(), unsafe.Sizeof(*))
		unsafeSlice(unsafe.Pointer(&), , )
		.copyall()
	} else {
		.count = 0xFFFF
		var  []pgid
		 := unsafeAdd(unsafe.Pointer(), unsafe.Sizeof(*))
		unsafeSlice(unsafe.Pointer(&), , +1)
		[0] = pgid()
		.copyall([1:])
	}

	return nil
}

// reload reads the freelist from a page and filters out pending items.
func ( *freelist) ( *page) {
	.read()

	// Build a cache of only pending pages.
	 := make(map[pgid]bool)
	for ,  := range .pending {
		for ,  := range .ids {
			[] = true
		}
	}

	// Check each page in the freelist and build a new available freelist
	// with any pages not in the pending lists.
	var  []pgid
	for ,  := range .getFreePageIDs() {
		if ![] {
			 = append(, )
		}
	}

	.readIDs()
}

// noSyncReload reads the freelist from pgids and filters out pending items.
func ( *freelist) ( []pgid) {
	// Build a cache of only pending pages.
	 := make(map[pgid]bool)
	for ,  := range .pending {
		for ,  := range .ids {
			[] = true
		}
	}

	// Check each page in the freelist and build a new available freelist
	// with any pages not in the pending lists.
	var  []pgid
	for ,  := range  {
		if ![] {
			 = append(, )
		}
	}

	.readIDs()
}

// reindex rebuilds the free cache based on available and pending free lists.
func ( *freelist) () {
	 := .getFreePageIDs()
	.cache = make(map[pgid]bool, len())
	for ,  := range  {
		.cache[] = true
	}
	for ,  := range .pending {
		for ,  := range .ids {
			.cache[] = true
		}
	}
}

// arrayMergeSpans try to merge list of pages(represented by pgids) with existing spans but using array
func ( *freelist) ( pgids) {
	sort.Sort()
	.ids = pgids(.ids).merge()
}