//
// 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;

    [Flags]
    internal enum IndexEntryFlags : ushort
    {
        None = 0x00,
        Node = 0x01,
        End = 0x02
    }

    internal class IndexEntry
    {
        public const int EndNodeSize = 0x18;

        protected IndexEntryFlags _flags;
        protected long _vcn; // Only valid if Node flag set

        protected byte[] _keyBuffer;
        protected byte[] _dataBuffer;

        private bool _isFileIndexEntry;

        public IndexEntry(bool isFileIndexEntry)
        {
            _isFileIndexEntry = isFileIndexEntry;
        }

        public IndexEntry(IndexEntry toCopy, byte[] newKey, byte[] newData)
        {
            _isFileIndexEntry = toCopy._isFileIndexEntry;
            _flags = toCopy._flags;
            _vcn = toCopy._vcn;
            _keyBuffer = newKey;
            _dataBuffer = newData;
        }

        public IndexEntry(byte[] key, byte[] data, bool isFileIndexEntry)
        {
            _isFileIndexEntry = isFileIndexEntry;
            _flags = IndexEntryFlags.None;
            _keyBuffer = key;
            _dataBuffer = data;
        }

        public byte[] KeyBuffer
        {
            get { return _keyBuffer; }
            set { _keyBuffer = value; }
        }

        public byte[] DataBuffer
        {
            get { return _dataBuffer; }
            set { _dataBuffer = value; }
        }

        public IndexEntryFlags Flags
        {
            get { return _flags; }
            set { _flags = value; }
        }

        public long ChildrenVirtualCluster
        {
            get { return _vcn; }
            set { _vcn = value; }
        }

        public virtual int Size
        {
            get
            {
                int size = 0x10; // start of variable data

                if ((_flags & IndexEntryFlags.End) == 0)
                {
                    size += _keyBuffer.Length;
                    size += IsFileIndexEntry ? 0 : _dataBuffer.Length;
                }

                size = Utilities.RoundUp(size, 8);

                if ((_flags & IndexEntryFlags.Node) != 0)
                {
                    size += 8;
                }

                return size;
            }
        }

        protected bool IsFileIndexEntry
        {
            get { return _isFileIndexEntry; }
        }

        public virtual void Read(byte[] buffer, int offset)
        {
            ushort dataOffset = Utilities.ToUInt16LittleEndian(buffer, offset + 0x00);
            ushort dataLength = Utilities.ToUInt16LittleEndian(buffer, offset + 0x02);
            ushort length = Utilities.ToUInt16LittleEndian(buffer, offset + 0x08);
            ushort keyLength = Utilities.ToUInt16LittleEndian(buffer, offset + 0x0A);
            _flags = (IndexEntryFlags)Utilities.ToUInt16LittleEndian(buffer, offset + 0x0C);

            if ((_flags & IndexEntryFlags.End) == 0)
            {
                _keyBuffer = new byte[keyLength];
                Array.Copy(buffer, offset + 0x10, _keyBuffer, 0, keyLength);

                if (IsFileIndexEntry)
                {
                    // Special case, for file indexes, the MFT ref is held where the data offset & length go
                    _dataBuffer = new byte[8];
                    Array.Copy(buffer, offset + 0x00, _dataBuffer, 0, 8);
                }
                else
                {
                    _dataBuffer = new byte[dataLength];
                    Array.Copy(buffer, offset + 0x10 + keyLength, _dataBuffer, 0, dataLength);
                }
            }

            if ((_flags & IndexEntryFlags.Node) != 0)
            {
                _vcn = Utilities.ToInt64LittleEndian(buffer, offset + length - 8);
            }
        }

        public virtual void WriteTo(byte[] buffer, int offset)
        {
            ushort length = (ushort)Size;

            if ((_flags & IndexEntryFlags.End) == 0)
            {
                ushort keyLength = (ushort)_keyBuffer.Length;

                if (IsFileIndexEntry)
                {
                    Array.Copy(_dataBuffer, 0, buffer, offset + 0x00, 8);
                }
                else
                {
                    ushort dataOffset = (ushort)(IsFileIndexEntry ? 0 : (0x10 + keyLength));
                    ushort dataLength = (ushort)_dataBuffer.Length;

                    Utilities.WriteBytesLittleEndian(dataOffset, buffer, offset + 0x00);
                    Utilities.WriteBytesLittleEndian(dataLength, buffer, offset + 0x02);
                    Array.Copy(_dataBuffer, 0, buffer, offset + dataOffset, _dataBuffer.Length);
                }

                Utilities.WriteBytesLittleEndian(keyLength, buffer, offset + 0x0A);
                Array.Copy(_keyBuffer, 0, buffer, offset + 0x10, _keyBuffer.Length);
            }
            else
            {
                Utilities.WriteBytesLittleEndian((ushort)0, buffer, offset + 0x00); // dataOffset
                Utilities.WriteBytesLittleEndian((ushort)0, buffer, offset + 0x02); // dataLength
                Utilities.WriteBytesLittleEndian((ushort)0, buffer, offset + 0x0A); // keyLength
            }

            Utilities.WriteBytesLittleEndian(length, buffer, offset + 0x08);
            Utilities.WriteBytesLittleEndian((ushort)_flags, buffer, offset + 0x0C);
            if ((_flags & IndexEntryFlags.Node) != 0)
            {
                Utilities.WriteBytesLittleEndian(_vcn, buffer, offset + length - 8);
            }
        }
    }
}
