//
// Copyright (c) 2008-2011, Kenneth Bell
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//

namespace DiscUtils.Ntfs
{
    using System;
    using System.IO;

    internal abstract class FixupRecordBase
    {
        private int _sectorSize;

        private string _magic;
        private ushort _updateSequenceOffset;
        private ushort _updateSequenceCount;

        private ushort _updateSequenceNumber;
        private ushort[] _updateSequenceArray;

        public FixupRecordBase(string magic, int sectorSize)
        {
            _magic = magic;
            _sectorSize = sectorSize;
        }

        public FixupRecordBase(string magic, int sectorSize, int recordLength)
        {
            Initialize(magic, sectorSize, recordLength);
        }

        public string Magic
        {
            get { return _magic; }
        }

        public ushort UpdateSequenceOffset
        {
            get { return _updateSequenceOffset; }
        }

        public ushort UpdateSequenceCount
        {
            get { return _updateSequenceCount; }
        }

        public ushort UpdateSequenceNumber
        {
            get { return _updateSequenceNumber; }
        }

        public int UpdateSequenceSize
        {
            get { return _updateSequenceCount * 2; }
        }

        public int Size
        {
            get
            {
                return CalcSize();
            }
        }

        public void FromBytes(byte[] buffer, int offset)
        {
            FromBytes(buffer, offset, false);
        }

        public void FromBytes(byte[] buffer, int offset, bool ignoreMagic)
        {
            string diskMagic = Utilities.BytesToString(buffer, offset + 0x00, 4);
            if (_magic == null)
            {
                _magic = diskMagic;
            }
            else
            {
                if (diskMagic != _magic && ignoreMagic)
                {
                    return;
                }

                if (diskMagic != _magic)
                {
                    throw new IOException("Corrupt record");
                }
            }

            _updateSequenceOffset = Utilities.ToUInt16LittleEndian(buffer, offset + 0x04);
            _updateSequenceCount = Utilities.ToUInt16LittleEndian(buffer, offset + 0x06);

            _updateSequenceNumber = Utilities.ToUInt16LittleEndian(buffer, offset + _updateSequenceOffset);
            _updateSequenceArray = new ushort[_updateSequenceCount - 1];
            for (int i = 0; i < _updateSequenceArray.Length; ++i)
            {
                _updateSequenceArray[i] = Utilities.ToUInt16LittleEndian(buffer, offset + _updateSequenceOffset + (2 * (i + 1)));
            }

            UnprotectBuffer(buffer, offset);

            Read(buffer, offset);
        }

        public void ToBytes(byte[] buffer, int offset)
        {
            _updateSequenceOffset = Write(buffer, offset);

            ProtectBuffer(buffer, offset);

            Utilities.StringToBytes(_magic, buffer, offset + 0x00, 4);
            Utilities.WriteBytesLittleEndian(_updateSequenceOffset, buffer, offset + 0x04);
            Utilities.WriteBytesLittleEndian(_updateSequenceCount, buffer, offset + 0x06);

            Utilities.WriteBytesLittleEndian(_updateSequenceNumber, buffer, offset + _updateSequenceOffset);
            for (int i = 0; i < _updateSequenceArray.Length; ++i)
            {
                Utilities.WriteBytesLittleEndian(_updateSequenceArray[i], buffer, offset + _updateSequenceOffset + (2 * (i + 1)));
            }
        }

        protected void Initialize(string magic, int sectorSize, int recordLength)
        {
            _magic = magic;
            _sectorSize = sectorSize;
            _updateSequenceCount = (ushort)(1 + Utilities.Ceil(recordLength, Sizes.Sector));
            _updateSequenceNumber = 1;
            _updateSequenceArray = new ushort[_updateSequenceCount - 1];
        }

        protected abstract void Read(byte[] buffer, int offset);

        protected abstract ushort Write(byte[] buffer, int offset);

        protected abstract int CalcSize();

        private void UnprotectBuffer(byte[] buffer, int offset)
        {
            // First do validation check - make sure the USN matches on all sectors)
            for (int i = 0; i < _updateSequenceArray.Length; ++i)
            {
                if (_updateSequenceNumber != Utilities.ToUInt16LittleEndian(buffer, offset + (Sizes.Sector * (i + 1)) - 2))
                {
                    throw new IOException("Corrupt file system record found");
                }
            }

            // Now replace the USNs with the actual data from the sequence array
            for (int i = 0; i < _updateSequenceArray.Length; ++i)
            {
                Utilities.WriteBytesLittleEndian(_updateSequenceArray[i], buffer, offset + (Sizes.Sector * (i + 1)) - 2);
            }
        }

        private void ProtectBuffer(byte[] buffer, int offset)
        {
            _updateSequenceNumber++;

            // Read in the bytes that are replaced by the USN
            for (int i = 0; i < _updateSequenceArray.Length; ++i)
            {
                _updateSequenceArray[i] = Utilities.ToUInt16LittleEndian(buffer, offset + (Sizes.Sector * (i + 1)) - 2);
            }

            // Overwrite the bytes that are replaced with the USN
            for (int i = 0; i < _updateSequenceArray.Length; ++i)
            {
                Utilities.WriteBytesLittleEndian(_updateSequenceNumber, buffer, offset + (Sizes.Sector * (i + 1)) - 2);
            }
        }
    }
}
