/// <summary>
        /// Return added pages when occurs an rollback transaction (run this only in rollback). Create new transactionID and add into
        /// Log file all new pages as EmptyPage in a linked order - also, update SharedPage before store
        /// </summary>
        private void ReturnNewPages()
        {
            // create new transaction ID
            var transactionID = _walIndex.NextTransactionID();

            // now lock header to update LastTransactionID/FreePageList
            lock (_header)
            {
                // persist all empty pages into wal-file
                var pagePositions = new Dictionary <uint, PagePosition>();

                IEnumerable <PageBuffer> source()
                {
                    // create list of empty pages with forward link pointer
                    for (var i = 0; i < _transPages.NewPages.Count; i++)
                    {
                        var pageID = _transPages.NewPages[i];
                        var next   = i < _transPages.NewPages.Count - 1 ? _transPages.NewPages[i + 1] : _header.FreeEmptyPageID;

                        var buffer = _disk.NewPage();

                        var page = new BasePage(buffer, pageID, PageType.Empty)
                        {
                            NextPageID    = next,
                            TransactionID = transactionID
                        };

                        yield return(page.UpdateBuffer());

                        // update wal
                        pagePositions[pageID] = new PagePosition(pageID, buffer.Position);

                        if (_disposed)
                        {
                            yield break;
                        }
                    }

                    // update header page with my new transaction ID
                    _header.TransactionID   = transactionID;
                    _header.FreeEmptyPageID = _transPages.NewPages[0];
                    _header.IsConfirmed     = true;

                    // clone header buffer
                    var buf   = _header.UpdateBuffer();
                    var clone = _disk.NewPage();

                    Buffer.BlockCopy(buf.Array, buf.Offset, clone.Array, clone.Offset, clone.Count);

                    yield return(clone);
                };

                // create a header save point before any change
                var safepoint = _header.Savepoint();

                try
                {
                    // write all pages (including new header)
                    _disk.WriteAsync(source());
                }
                catch
                {
                    // must revert all header content if any error occurs during header change
                    _header.Restore(safepoint);
                    throw;
                }

                // now confirm this transaction to wal
                _walIndex.ConfirmTransaction(transactionID, pagePositions.Values);
            }
        }
        /// <summary>
        /// Persist all dirty in-memory pages (in all snapshots) and clear local pages list (even clean pages)
        /// </summary>
        private int PersistDirtyPages(bool commit)
        {
            var dirty = 0;

            // inner method to get all dirty pages
            IEnumerable <PageBuffer> source()
            {
                // get all dirty pages from all write snapshots
                // can include (or not) collection pages
                // update DirtyPagesLog inside transPage for all dirty pages was write on disk
                var pages = _snapshots.Values
                            .Where(x => x.Mode == LockMode.Write)
                            .SelectMany(x => x.GetWritablePages(true, commit));

                // mark last dirty page as confirmed only if there is no header change in commit
                var markLastAsConfirmed = commit && _transPages.HeaderChanged == false;

                // loop across all dirty pages
                if (markLastAsConfirmed)
                {
                    // neet use "IsLast" method to get when loop are last item
                    foreach (var page in pages.IsLast())
                    {
                        // update page transactionID
                        page.Item.TransactionID = _transactionID;

                        // if last page, mask as confirm
                        if (page.IsLast)
                        {
                            page.Item.IsConfirmed = true;
                        }

                        var buffer = page.Item.UpdateBuffer();

                        // buffer position will be set at end of file (it´s always log file)
                        yield return(buffer);

                        dirty++;

                        _transPages.DirtyPages[page.Item.PageID] = new PagePosition(page.Item.PageID, buffer.Position);
                    }
                }
                else
                {
                    // avoid use "IsLast" when was not needed (better performance)
                    foreach (var page in pages)
                    {
                        // update page transactionID
                        page.TransactionID = _transactionID;

                        var buffer = page.UpdateBuffer();

                        // buffer position will be set at end of file (it´s always log file)
                        yield return(buffer);

                        dirty++;

                        _transPages.DirtyPages[page.PageID] = new PagePosition(page.PageID, buffer.Position);
                    }
                }

                // in commit with header page change, last page will be header
                if (commit && _transPages.HeaderChanged)
                {
                    // lock header page to avoid concurrency when writing on header
                    lock (_header)
                    {
                        var newEmptyPageID = _header.FreeEmptyPageID;

                        // if has deleted pages in this transaction, fix FreeEmptyPageID
                        if (_transPages.DeletedPages > 0)
                        {
                            // now, my free list will starts with first page ID
                            newEmptyPageID = _transPages.FirstDeletedPageID;

                            // if free empty list was not empty, let's fix my last page
                            if (_header.FreeEmptyPageID != uint.MaxValue)
                            {
                                var empty = _disk.NewPage();

                                // to avoid read a empty page from disk I will create new page as empty and override it
                                var lastDeletedPage = new BasePage(empty, _transPages.LastDeletedPageID, PageType.Empty)
                                {
                                    // update nextPageID of last deleted page to old first page ID
                                    NextPageID    = _header.FreeEmptyPageID,
                                    TransactionID = _transactionID
                                };

                                // this page will write twice on wal, but no problem, only this last version will be saved on data file
                                yield return(lastDeletedPage.UpdateBuffer());
                            }
                        }

                        // update this confirm page with current transactionID
                        _header.FreeEmptyPageID = newEmptyPageID;
                        _header.TransactionID   = _transactionID;

                        // this header page will be marked as confirmed page in log file
                        _header.IsConfirmed = true;

                        // invoke all header callbacks (new/drop collections)
                        _transPages.OnCommit(_header);

                        // clone header page
                        var buffer = _header.UpdateBuffer();
                        var clone  = _disk.NewPage();

                        // mem copy from current header to new header clone
                        Buffer.BlockCopy(buffer.Array, buffer.Offset, clone.Array, clone.Offset, clone.Count);

                        // persist header in log file
                        yield return(clone);
                    }
                }
            };

            // write all dirty pages, in sequence on log-file and store references into log pages on transPages
            // (works only for Write snapshots)
            var count = _disk.WriteAsync(source());

            // now, discard all clean pages (because those pages are writable and must be readable)
            // from write snapshots
            _disk.DiscardPages(_snapshots.Values
                               .Where(x => x.Mode == LockMode.Write)
                               .SelectMany(x => x.GetWritablePages(false, commit))
                               .Select(x => x.Buffer), false);

            return(count);
        }