/* Copyright (C) 2014 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
 * 
 * You can redistribute this program and/or modify it under the terms of
 * the GNU Lesser Public License as published by the Free Software Foundation,
 * either version 3 of the License, or (at your option) any later version.
 */
using System;
using System.Collections.Generic;
using System.Text;
using Utilities;

namespace DiskAccessLibrary.LogicalDiskManager
{
    [Flags]
    public enum ExtentFlags : uint
    {
        Recover = 0x0008,
        KDetach = 0x0010,
        Relocate = 0x0020,
        BootExtended = 0x0040,
        OrigBoot = 0x0100,
        Volatile = 0x1000,
    }

    // a.k.a. subdisk
    public class ExtentRecord : DatabaseRecord
    {
        public ExtentFlags ExtentFlags;
        public ulong CommitTransactionID;
        public ulong DiskOffsetLBA;        // extent location relative to DataStartLBA (from the private header)
        public ulong OffsetInColumnLBA;    // the location of this extent in the column (for single column volumes this means the offset in the volume)
        public ulong SizeLBA;     // PaddedVarUlong
        public ulong ComponentId;
        public ulong DiskId;
        public uint ColumnIndex; // On Striped / RAID-5 volumes, this is the interleave order, note that column can be comprised of one or more extents
        public ulong UnknownTransactionID;
        public uint Unknown1;
        public ulong HiddenCount; // PaddedVarUlong, Hidden sectors?
        
        public ExtentRecord()
        {
            this.RecordRevision = 3;
            this.RecordType = RecordType.Extent;
        }

        public ExtentRecord(List<DatabaseRecordFragment> fragments) : base(fragments)
        {
            // Data begins at 0x10 (VBLK header is at 0x00)
            int offset = 0x00; // relative to Data
            ReadCommonFields(this.Data, ref offset);
            if (RecordRevision != 3)
            {
                throw new NotImplementedException("Unsupported record revision");
            }
            ExtentFlags = (ExtentFlags)BigEndianReader.ReadUInt32(this.Data, ref offset);
            CommitTransactionID = BigEndianReader.ReadUInt64(this.Data, ref offset);
            DiskOffsetLBA = BigEndianReader.ReadUInt64(this.Data, ref offset);
            OffsetInColumnLBA = BigEndianReader.ReadUInt64(this.Data, ref offset);
            SizeLBA = ReadVarULong(this.Data, ref offset);
            ComponentId = ReadVarULong(this.Data, ref offset);
            DiskId = ReadVarULong(this.Data, ref offset);

            if (HasColumnIndexFlag)
            {
                ColumnIndex = ReadVarUInt(this.Data, ref offset);
            }

            if (HasUnknownTransactionIDFlag)
            {
                UnknownTransactionID = BigEndianReader.ReadUInt64(this.Data, ref offset);
            }

            if (HasUnknown1Flag)
            {
                Unknown1 = ReadVarUInt(this.Data, ref offset);
            }

            if (HasHiddenFlag)
            {
                HiddenCount = ReadVarULong(this.Data, ref offset);
            }
        }

        public override byte[] GetDataBytes()
        {
            int dataLength = 36; // fixed length components
            dataLength += VarULongSize(ExtentId);
            dataLength += Name.Length + 1;
            dataLength += PaddedVarULongSize(SizeLBA);
            dataLength += VarULongSize(ComponentId);
            dataLength += VarULongSize(DiskId);
            if (HasColumnIndexFlag)
            {
                dataLength += VarULongSize(ColumnIndex);
            }
            if (HasUnknownTransactionIDFlag)
            {
                dataLength += 8;
            }
            if (HasUnknown1Flag)
            {
                dataLength += VarULongSize(Unknown1);
            }
            if (HasHiddenFlag)
            {
                dataLength += PaddedVarULongSize(HiddenCount);
            }
            
            byte[] data = new byte[dataLength];
            int offset = 0x00;
            WriteCommonFields(data, ref offset);
            BigEndianWriter.WriteUInt32(data, ref offset, (uint)ExtentFlags);
            BigEndianWriter.WriteUInt64(data, ref offset, CommitTransactionID);
            BigEndianWriter.WriteUInt64(data, ref offset, DiskOffsetLBA);
            BigEndianWriter.WriteUInt64(data, ref offset, OffsetInColumnLBA);
            WritePaddedVarULong(data, ref offset, SizeLBA);
            WriteVarULong(data, ref offset, ComponentId);
            WriteVarULong(data, ref offset, DiskId);

            if (HasColumnIndexFlag)
            {
                WriteVarUInt(data, ref offset, ColumnIndex);
            }
            if (HasUnknownTransactionIDFlag)
            {
                BigEndianWriter.WriteUInt64(data, ref offset, UnknownTransactionID);
            }
            if (HasUnknown1Flag)
            {
                WriteVarUInt(data, ref offset, Unknown1);
            }
            if (HasHiddenFlag)
            {
                WritePaddedVarULong(data, ref offset, HiddenCount);
            }
            
            return data;
        }

        public ulong ExtentId
        {
            get
            {
                return this.Id;
            }
        }

        /// <summary>
        /// Hidden sectors?
        /// </summary>
        public bool HasHiddenFlag
        {
            get
            {
                return ((Flags & 0x02) != 0);
            }
        }

        public bool HasColumnIndexFlag
        {
            get
            {
                return ((Flags & 0x08) != 0);
            }
            set
            {
                if (value)
                {
                    this.Flags = 0x08;
                }
                else
                {
                    this.Flags &= 0xF7;
                }
            }
        }

        public bool HasUnknownTransactionIDFlag
        {
            get
            {
                return ((Flags & 0x20) != 0);
            }
        }

        /// <summary>
        /// Windows Vista and newer set this flag by default, earlier versions do not
        /// </summary>
        public bool HasUnknown1Flag
        {
            get
            {
                return ((Flags & 0x40) != 0);
            }
        }
    }
}