public void FieldOffsetsAreAutoAssigned(DbaseField[] fields, ByteOffset[] expected)
        {
            var sut = new AnonymousDbaseSchema(fields);

            Assert.Equal(
                sut.Fields.Select(field => field.Offset).ToArray(),
                expected);
        }
        public void ConstructionUsingFieldsHasExpectedResult()
        {
            var fields = _fixture.GenerateDbaseFields();
            var length = fields.Aggregate(DbaseRecordLength.Initial, (current, field) => current.Plus(field.Length));

            var sut = new AnonymousDbaseSchema(fields);

            Assert.Equal(fields, sut.Fields);
            Assert.Equal(length, sut.Length);
        }
        public static DbaseFileHeader Read(BinaryReader reader)
        {
            if (reader == null)
            {
                throw new ArgumentNullException(nameof(reader));
            }

            if (reader.ReadByte() != ExpectedDbaseFormat)
            {
                throw new DbaseFileHeaderException("The database file type must be 3 (dBase III).");
            }

            var lastUpdated = new DateTime(reader.ReadByte() + 1900, reader.ReadByte(), reader.ReadByte(), 0, 0, 0,
                                           DateTimeKind.Unspecified);
            var recordCount  = new DbaseRecordCount(reader.ReadInt32());
            var headerLength = reader.ReadInt16();
            var fieldCount   = (headerLength - HeaderMetaDataSize) / FieldMetaDataSize;

            if (fieldCount > DbaseSchema.MaximumFieldCount)
            {
                throw new DbaseFileHeaderException(
                          $"The database file can not contain more than {DbaseSchema.MaximumFieldCount} fields.");
            }

            var recordLength = new DbaseRecordLength(reader.ReadInt16());

            reader.ReadBytes(17);
            var rawCodePage = reader.ReadByte();

            if (!DbaseCodePage.TryParse(rawCodePage, out var codePage))
            {
                throw new DbaseFileHeaderException($"The database code page {rawCodePage} is not supported.");
            }

            reader.ReadBytes(2);
            var fields = new DbaseField[fieldCount];

            for (var recordFieldIndex = 0; recordFieldIndex < fieldCount; recordFieldIndex++)
            {
                fields[recordFieldIndex] = DbaseField.Read(reader);
            }

            // verify field offsets are aligned
            var offset = ByteOffset.Initial;

            foreach (var field in fields)
            {
                if (field.Offset != offset)
                {
                    throw new DbaseFileHeaderException(
                              $"The field {field.Name} does not have the expected offset {offset} but instead {field.Offset}. Please ensure the offset has been properly set for each field and that the order in which they appear in the record field layout matches their offset.");
                }

                offset = field.Offset.Plus(field.Length);
            }

            var schema = new AnonymousDbaseSchema(fields);

            if (recordLength != schema.Length)
            {
                throw new DbaseFileHeaderException(
                          $"The database file record length ({recordLength}) does not match the total length of all fields ({schema.Length}).");
            }

            if (reader.ReadByte() != Terminator)
            {
                throw new DbaseFileHeaderException("The database file header terminator is missing.");
            }

            // skip to first record
            var bytesToSkip = headerLength - (HeaderMetaDataSize + FieldMetaDataSize * fieldCount);

            reader.ReadBytes(bytesToSkip);
            return(new DbaseFileHeader(lastUpdated, codePage, recordCount, schema));
        }