private LogRecord CreateLogRecord(Rec rec, TransactionInfo transInfo, long logPos, long expectedVersion)
        {
            switch (rec.Type)
            {
            case Rec.RecType.Prepare:
            {
                int transOffset = transInfo.TransactionOffset;
                transInfo.TransactionOffset += 1;

                if (rec.Version == LogRecordVersion.LogRecordV0)
                {
                    return(CreateLogRecordV0(rec, transInfo, transOffset, logPos, expectedVersion,
                                             rec.Metadata == null ? rec.Id.ToByteArray() : FormatRecordMetadata(rec),
                                             PrepareFlags.Data
                                             | (transInfo.FirstPrepareId == rec.Id ? PrepareFlags.TransactionBegin : PrepareFlags.None)
                                             | (transInfo.LastPrepareId == rec.Id ? PrepareFlags.TransactionEnd : PrepareFlags.None)
                                             | (rec.Metadata == null ? PrepareFlags.None : PrepareFlags.IsJson)));
                }
                return(LogRecord.Prepare(logPos,
                                         Guid.NewGuid(),
                                         rec.Id,
                                         transInfo.TransactionPosition,
                                         transOffset,
                                         rec.StreamId,
                                         expectedVersion,
                                         rec.PrepareFlags
                                         | (transInfo.FirstPrepareId == rec.Id ? PrepareFlags.TransactionBegin : PrepareFlags.None)
                                         | (transInfo.LastPrepareId == rec.Id ? PrepareFlags.TransactionEnd : PrepareFlags.None)
                                         | (rec.Metadata == null ? PrepareFlags.None : PrepareFlags.IsJson),
                                         rec.EventType,
                                         rec.Metadata == null ? rec.Id.ToByteArray() : FormatRecordMetadata(rec),
                                         null,
                                         rec.TimeStamp));
            }

            case Rec.RecType.Delete:
            {
                int transOffset = transInfo.TransactionOffset;
                transInfo.TransactionOffset += 1;

                if (rec.Version == LogRecordVersion.LogRecordV0)
                {
                    return(CreateLogRecordV0(rec, transInfo, transOffset, logPos, expectedVersion,
                                             LogRecord.NoData,
                                             PrepareFlags.StreamDelete
                                             | (transInfo.FirstPrepareId == rec.Id ? PrepareFlags.TransactionBegin : PrepareFlags.None)
                                             | (transInfo.LastPrepareId == rec.Id ? PrepareFlags.TransactionEnd : PrepareFlags.None)));
                }

                return(LogRecord.Prepare(logPos,
                                         Guid.NewGuid(),
                                         rec.Id,
                                         transInfo.TransactionPosition,
                                         transOffset,
                                         rec.StreamId,
                                         expectedVersion,
                                         PrepareFlags.StreamDelete
                                         | (transInfo.FirstPrepareId == rec.Id ? PrepareFlags.TransactionBegin : PrepareFlags.None)
                                         | (transInfo.LastPrepareId == rec.Id ? PrepareFlags.TransactionEnd : PrepareFlags.None),
                                         rec.EventType,
                                         LogRecord.NoData,
                                         null,
                                         rec.TimeStamp));
            }

            case Rec.RecType.TransStart:
            case Rec.RecType.TransEnd:
            {
                if (rec.Version == LogRecordVersion.LogRecordV0)
                {
                    return(CreateLogRecordV0(rec, transInfo, -1, logPos, expectedVersion,
                                             LogRecord.NoData,
                                             (transInfo.FirstPrepareId == rec.Id ? PrepareFlags.TransactionBegin : PrepareFlags.None)
                                             | (transInfo.LastPrepareId == rec.Id ? PrepareFlags.TransactionEnd : PrepareFlags.None)));
                }
                return(LogRecord.Prepare(logPos,
                                         Guid.NewGuid(),
                                         rec.Id,
                                         transInfo.TransactionPosition,
                                         -1,
                                         rec.StreamId,
                                         expectedVersion,
                                         (transInfo.FirstPrepareId == rec.Id ? PrepareFlags.TransactionBegin : PrepareFlags.None)
                                         | (transInfo.LastPrepareId == rec.Id ? PrepareFlags.TransactionEnd : PrepareFlags.None),
                                         rec.EventType,
                                         LogRecord.NoData,
                                         null,
                                         rec.TimeStamp));
            }

            case Rec.RecType.Commit:
            {
                if (rec.Version == LogRecordVersion.LogRecordV0)
                {
                    return(new CommitLogRecord(logPos, Guid.NewGuid(), transInfo.TransactionPosition, DateTime.UtcNow, transInfo.TransactionEventNumber, LogRecordVersion.LogRecordV0));
                }
                return(LogRecord.Commit(logPos, Guid.NewGuid(), transInfo.TransactionPosition, transInfo.TransactionEventNumber));
            }

            default:
                throw new ArgumentOutOfRangeException();
            }
        }
        public DbResult CreateDb()
        {
            var records = new LogRecord[_chunkRecs.Count][];

            for (int i = 0; i < records.Length; ++i)
            {
                records[i] = new LogRecord[_chunkRecs[i].Length];
            }

            var transactions            = new Dictionary <int, TransactionInfo>();
            var streams                 = new Dictionary <string, StreamInfo>();
            var streamUncommitedVersion = new Dictionary <string, long>();

            for (int i = 0; i < _chunkRecs.Count; ++i)
            {
                for (int j = 0; j < _chunkRecs[i].Length; ++j)
                {
                    var rec = _chunkRecs[i][j];

                    TransactionInfo transInfo;
                    bool            transCreate = transactions.TryGetValue(rec.Transaction, out transInfo);
                    if (!transCreate)
                    {
                        if (rec.Type == Rec.RecType.Commit)
                        {
                            throw new Exception("Commit for non-existing transaction.");
                        }

                        transactions[rec.Transaction] = transInfo = new TransactionInfo(rec.StreamId, rec.Id, rec.Id);

                        streams[rec.StreamId] = new StreamInfo(-1);
                        streamUncommitedVersion[rec.StreamId] = -1;
                    }
                    else
                    {
                        if (rec.Type == Rec.RecType.TransStart)
                        {
                            throw new Exception(string.Format("Unexpected record type: {0}.", rec.Type));
                        }
                    }

                    if (transInfo.StreamId != rec.StreamId)
                    {
                        throw new Exception(string.Format("Wrong stream id for transaction. Transaction StreamId: {0}, record StreamId: {1}.",
                                                          transInfo.StreamId,
                                                          rec.StreamId));
                    }

                    if (rec.Type != Rec.RecType.Commit && transInfo.IsDelete)
                    {
                        throw new Exception("Transaction with records after delete record.");
                    }

                    if (rec.Type == Rec.RecType.Delete)
                    {
                        transInfo.IsDelete = true;
                    }

                    transInfo.LastPrepareId = rec.Id;
                }
            }

            for (int i = 0; i < _chunkRecs.Count; ++i)
            {
                var chunk = i == 0 ? _db.Manager.GetChunk(0) : _db.Manager.AddNewChunk();
                _db.Config.WriterCheckpoint.Write(i * (long)_db.Config.ChunkSize);

                for (int j = 0; j < _chunkRecs[i].Length; ++j)
                {
                    var rec       = _chunkRecs[i][j];
                    var transInfo = transactions[rec.Transaction];
                    var logPos    = _db.Config.WriterCheckpoint.ReadNonFlushed();

                    long streamVersion = streamUncommitedVersion[rec.StreamId];
                    if (streamVersion == -1 &&
                        rec.Type != Rec.RecType.TransStart &&
                        rec.Type != Rec.RecType.Prepare &&
                        rec.Type != Rec.RecType.Delete)
                    {
                        throw new Exception(string.Format("Stream {0} is empty.", rec.StreamId));
                    }
                    if (streamVersion == EventNumber.DeletedStream && rec.Type != Rec.RecType.Commit)
                    {
                        throw new Exception(string.Format("Stream {0} was deleted, but we need to write some more prepares.", rec.StreamId));
                    }

                    if (transInfo.FirstPrepareId == rec.Id)
                    {
                        transInfo.TransactionPosition    = logPos;
                        transInfo.TransactionEventNumber = streamVersion + 1;
                        transInfo.TransactionOffset      = 0;
                    }

                    LogRecord record;

                    var expectedVersion = transInfo.FirstPrepareId == rec.Id ? streamVersion : ExpectedVersion.Any;
                    switch (rec.Type)
                    {
                    case Rec.RecType.Prepare:
                    {
                        record = CreateLogRecord(rec, transInfo, logPos, expectedVersion);

                        if (SystemStreams.IsMetastream(rec.StreamId))
                        {
                            transInfo.StreamMetadata = rec.Metadata;
                        }

                        streamUncommitedVersion[rec.StreamId] += 1;
                        break;
                    }

                    case Rec.RecType.Delete:
                    {
                        record = CreateLogRecord(rec, transInfo, logPos, expectedVersion);

                        streamUncommitedVersion[rec.StreamId] = rec.Version == LogRecordVersion.LogRecordV0 ? int.MaxValue : EventNumber.DeletedStream;
                        break;
                    }

                    case Rec.RecType.TransStart:
                    case Rec.RecType.TransEnd:
                    {
                        record = CreateLogRecord(rec, transInfo, logPos, expectedVersion);
                        break;
                    }

                    case Rec.RecType.Commit:
                    {
                        record = CreateLogRecord(rec, transInfo, logPos, expectedVersion);

                        if (transInfo.StreamMetadata != null)
                        {
                            var streamId = SystemStreams.OriginalStreamOf(rec.StreamId);
                            if (!streams.ContainsKey(streamId))
                            {
                                streams.Add(streamId, new StreamInfo(-1));
                            }
                            streams[streamId].StreamMetadata = transInfo.StreamMetadata;
                        }

                        if (transInfo.IsDelete)
                        {
                            streams[rec.StreamId].StreamVersion = EventNumber.DeletedStream;
                        }
                        else
                        {
                            streams[rec.StreamId].StreamVersion = transInfo.TransactionEventNumber + transInfo.TransactionOffset - 1;
                        }
                        break;
                    }

                    default:
                        throw new ArgumentOutOfRangeException();
                    }

                    var writerRes = chunk.TryAppend(record);
                    if (!writerRes.Success)
                    {
                        throw new Exception(string.Format("Could not write log record: {0}", record));
                    }
                    _db.Config.WriterCheckpoint.Write(i * (long)_db.Config.ChunkSize + writerRes.NewPosition);

                    records[i][j] = record;
                }

                if (i < _chunkRecs.Count - 1 || (_completeLast && i == _chunkRecs.Count - 1))
                {
                    chunk.Complete();
                }
                else
                {
                    chunk.Flush();
                }
            }
            return(new DbResult(_db, records, streams));
        }
        public DbResult CreateDb()
        {
            var records = new LogRecord[_chunkRecs.Count][];
            for (int i = 0; i < records.Length; ++i)
            {
                records[i] = new LogRecord[_chunkRecs[i].Length];
            }

            var transactions = new Dictionary<int, TransactionInfo>();
            var streams = new Dictionary<string, StreamInfo>();
            var streamUncommitedVersion = new Dictionary<string, int>();

            for (int i = 0; i < _chunkRecs.Count; ++i)
            {
                for (int j = 0; j < _chunkRecs[i].Length; ++j)
                {
                    var rec = _chunkRecs[i][j];

                    TransactionInfo transInfo;
                    bool transCreate = transactions.TryGetValue(rec.Transaction, out transInfo);
                    if (!transCreate)
                    {
                        if (rec.Type == Rec.RecType.Commit)
                            throw new Exception("Commit for non-existing transaction.");

                        transactions[rec.Transaction] = transInfo = new TransactionInfo(rec.StreamId, rec.Id, rec.Id);

                        streams[rec.StreamId] = new StreamInfo(-1);
                        streamUncommitedVersion[rec.StreamId] = -1;
                    }
                    else
                    {
                        if (rec.Type == Rec.RecType.TransStart)
                            throw new Exception(string.Format("Not expected record type: {0}.", rec.Type));
                    }

                    if (transInfo.StreamId != rec.StreamId)
                    {
                        throw new Exception(string.Format("Wrong stream id for transaction. Transaction StreamId: {0}, record StreamId: {1}.",
                                                          transInfo.StreamId,
                                                          rec.StreamId));
                    }

                    if (rec.Type != Rec.RecType.Commit && transInfo.IsDelete)
                        throw new Exception("Transaction with records after delete record.");

                    if (rec.Type == Rec.RecType.Delete)
                        transInfo.IsDelete = true;
                    
                    transInfo.LastPrepareId = rec.Id;
                }
            }

            for (int i = 0; i < _chunkRecs.Count; ++i)
            {
                var chunk = i == 0 ? _db.Manager.GetChunk(0) : _db.Manager.AddNewChunk();
                _db.Config.WriterCheckpoint.Write(i * (long)_db.Config.ChunkSize);

                for (int j = 0; j < _chunkRecs[i].Length; ++j)
                {
                    var rec = _chunkRecs[i][j];
                    var transInfo = transactions[rec.Transaction];
                    var logPos = _db.Config.WriterCheckpoint.ReadNonFlushed();

                    int streamVersion = streamUncommitedVersion[rec.StreamId];
                    if (streamVersion == -1
                        && rec.Type != Rec.RecType.TransStart
                        && rec.Type != Rec.RecType.Prepare
                        && rec.Type != Rec.RecType.Delete)
                    {
                        throw new Exception(string.Format("Stream {0} is empty.", rec.StreamId));
                    }
                    if (streamVersion == EventNumber.DeletedStream && rec.Type != Rec.RecType.Commit)
                        throw new Exception(string.Format("Stream {0} was deleted, but we need to write some more prepares.", rec.StreamId));

                    if (transInfo.FirstPrepareId == rec.Id)
                    {
                        transInfo.TransactionPosition = logPos;
                        transInfo.TransactionEventNumber = streamVersion + 1;
                        transInfo.TransactionOffset = 0;
                    }

                    LogRecord record;

                    var expectedVersion = transInfo.FirstPrepareId == rec.Id ? streamVersion : ExpectedVersion.Any;
                    switch (rec.Type)
                    {
                        case Rec.RecType.Prepare:
                        {
                            int transOffset = transInfo.TransactionOffset;
                            transInfo.TransactionOffset += 1;

                            record = LogRecord.Prepare(logPos, 
                                                       Guid.NewGuid(), 
                                                       rec.Id, 
                                                       transInfo.TransactionPosition,
                                                       transOffset,
                                                       rec.StreamId,
                                                       expectedVersion,
                                                       PrepareFlags.Data
                                                       | (transInfo.FirstPrepareId == rec.Id ? PrepareFlags.TransactionBegin : PrepareFlags.None)
                                                       | (transInfo.LastPrepareId == rec.Id ? PrepareFlags.TransactionEnd : PrepareFlags.None)
                                                       | (rec.Metadata == null ? PrepareFlags.None : PrepareFlags.IsJson),
                                                       rec.EventType,
                                                       rec.Metadata == null ? rec.Id.ToByteArray() : FormatRecordMetadata(rec),
                                                       null,
                                                       rec.TimeStamp);
                            if (SystemStreams.IsMetastream(rec.StreamId))
                                transInfo.StreamMetadata = rec.Metadata;

                            streamUncommitedVersion[rec.StreamId] += 1;
                            break;
                        }

                        case Rec.RecType.Delete:
                        {
                            int transOffset = transInfo.TransactionOffset;
                            transInfo.TransactionOffset += 1;

                            record = LogRecord.Prepare(logPos, 
                                                       Guid.NewGuid(), 
                                                       rec.Id, 
                                                       transInfo.TransactionPosition,
                                                       transOffset,
                                                       rec.StreamId,
                                                       expectedVersion,
                                                       PrepareFlags.StreamDelete
                                                       | (transInfo.FirstPrepareId == rec.Id ? PrepareFlags.TransactionBegin : PrepareFlags.None)
                                                       | (transInfo.LastPrepareId == rec.Id ? PrepareFlags.TransactionEnd : PrepareFlags.None), 
                                                       rec.EventType,
                                                       LogRecord.NoData,
                                                       null,
                                                       rec.TimeStamp);
                            streamUncommitedVersion[rec.StreamId] = EventNumber.DeletedStream;
                            break;
                        }

                        case Rec.RecType.TransStart:
                        case Rec.RecType.TransEnd:
                        {
                            record = LogRecord.Prepare(logPos,
                                                       Guid.NewGuid(),
                                                       rec.Id,
                                                       transInfo.TransactionPosition,
                                                       -1,
                                                       rec.StreamId,
                                                       expectedVersion,
                                                       (transInfo.FirstPrepareId == rec.Id ? PrepareFlags.TransactionBegin : PrepareFlags.None)
                                                       | (transInfo.LastPrepareId == rec.Id ? PrepareFlags.TransactionEnd : PrepareFlags.None),
                                                       rec.EventType,
                                                       LogRecord.NoData,
                                                       null,
                                                       rec.TimeStamp);
                            break;
                        }
                        case Rec.RecType.Commit:
                        {
                            record = LogRecord.Commit(logPos, Guid.NewGuid(), transInfo.TransactionPosition, transInfo.TransactionEventNumber);

                            if (transInfo.StreamMetadata != null)
                            {
                                var streamId = SystemStreams.OriginalStreamOf(rec.StreamId);
                                if (!streams.ContainsKey(streamId))
                                    streams.Add(streamId, new StreamInfo(-1));
                                streams[streamId].StreamMetadata = transInfo.StreamMetadata;
                            }

                            if (transInfo.IsDelete)
                                streams[rec.StreamId].StreamVersion = EventNumber.DeletedStream;
                            else
                                streams[rec.StreamId].StreamVersion = transInfo.TransactionEventNumber + transInfo.TransactionOffset - 1;
                            break;
                        }
                        default:
                            throw new ArgumentOutOfRangeException();
                    }

                    var writerRes = chunk.TryAppend(record);
                    if (!writerRes.Success)
                        throw new Exception(string.Format("Couldn't write log record: {0}", record));
                    _db.Config.WriterCheckpoint.Write(i * (long)_db.Config.ChunkSize + writerRes.NewPosition);

                    records[i][j] = record;
                }

                if (i < _chunkRecs.Count-1 || (_completeLast && i == _chunkRecs.Count - 1))
                    chunk.Complete();
                else
                    chunk.Flush();
            }
            return new DbResult(_db, records, streams);
        }