//
// 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.Collections.Generic;
    using System.IO;

    internal sealed class IndexRoot : IByteArraySerializable, IDiagnosticTraceable
    {
        public const int HeaderOffset = 0x10;

        private uint _attrType;
        private AttributeCollationRule _collationRule;
        private uint _indexAllocationEntrySize;
        private byte _rawClustersPerIndexRecord;

        public uint AttributeType
        {
            get { return _attrType; }
            set { _attrType = value; }
        }

        public AttributeCollationRule CollationRule
        {
            get { return _collationRule; }
            set { _collationRule = value; }
        }

        public uint IndexAllocationSize
        {
            get { return _indexAllocationEntrySize; }
            set { _indexAllocationEntrySize = value; }
        }

        public byte RawClustersPerIndexRecord
        {
            get { return _rawClustersPerIndexRecord; }
            set { _rawClustersPerIndexRecord = value; }
        }

        public int Size
        {
            get { return 16; }
        }

        public IComparer<byte[]> GetCollator(UpperCase upCase)
        {
            switch (_collationRule)
            {
                case AttributeCollationRule.Filename:
                    return new FileNameComparer(upCase);
                case AttributeCollationRule.SecurityHash:
                    return new SecurityHashComparer();
                case AttributeCollationRule.UnsignedLong:
                    return new UnsignedLongComparer();
                case AttributeCollationRule.MultipleUnsignedLongs:
                    return new MultipleUnsignedLongComparer();
                case AttributeCollationRule.Sid:
                    return new SidComparer();
                default:
                    throw new NotImplementedException();
            }
        }

        public int ReadFrom(byte[] buffer, int offset)
        {
            _attrType = Utilities.ToUInt32LittleEndian(buffer, 0x00);
            _collationRule = (AttributeCollationRule)Utilities.ToUInt32LittleEndian(buffer, 0x04);
            _indexAllocationEntrySize = Utilities.ToUInt32LittleEndian(buffer, 0x08);
            _rawClustersPerIndexRecord = buffer[0x0C];
            return 16;
        }

        public void WriteTo(byte[] buffer, int offset)
        {
            Utilities.WriteBytesLittleEndian(_attrType, buffer, 0);
            Utilities.WriteBytesLittleEndian((uint)_collationRule, buffer, 0x04);
            Utilities.WriteBytesLittleEndian(_indexAllocationEntrySize, buffer, 0x08);
            Utilities.WriteBytesLittleEndian(_rawClustersPerIndexRecord, buffer, 0x0C);
        }

        public void Dump(TextWriter writer, string indent)
        {
            writer.WriteLine(indent + "                Attr Type: " + _attrType);
            writer.WriteLine(indent + "           Collation Rule: " + _collationRule);
            writer.WriteLine(indent + "         Index Alloc Size: " + _indexAllocationEntrySize);
            writer.WriteLine(indent + "  Raw Clusters Per Record: " + _rawClustersPerIndexRecord);
        }

        private sealed class SecurityHashComparer : IComparer<byte[]>
        {
            public int Compare(byte[] x, byte[] y)
            {
                if (x == null && y == null)
                {
                    return 0;
                }
                else if (y == null)
                {
                    return -1;
                }
                else if (x == null)
                {
                    return 1;
                }

                uint xHash = Utilities.ToUInt32LittleEndian(x, 0);
                uint yHash = Utilities.ToUInt32LittleEndian(y, 0);

                if (xHash < yHash)
                {
                    return -1;
                }
                else if (xHash > yHash)
                {
                    return 1;
                }

                uint xId = Utilities.ToUInt32LittleEndian(x, 4);
                uint yId = Utilities.ToUInt32LittleEndian(y, 4);
                if (xId < yId)
                {
                    return -1;
                }
                else if (xId > yId)
                {
                    return 1;
                }
                else
                {
                    return 0;
                }
            }
        }

        private sealed class UnsignedLongComparer : IComparer<byte[]>
        {
            public int Compare(byte[] x, byte[] y)
            {
                if (x == null && y == null)
                {
                    return 0;
                }
                else if (y == null)
                {
                    return -1;
                }
                else if (x == null)
                {
                    return 1;
                }

                uint xVal = Utilities.ToUInt32LittleEndian(x, 0);
                uint yVal = Utilities.ToUInt32LittleEndian(y, 0);

                if (xVal < yVal)
                {
                    return -1;
                }
                else if (xVal > yVal)
                {
                    return 1;
                }

                return 0;
            }
        }

        private sealed class MultipleUnsignedLongComparer : IComparer<byte[]>
        {
            public int Compare(byte[] x, byte[] y)
            {
                for (int i = 0; i < x.Length / 4; ++i)
                {
                    if (x == null && y == null)
                    {
                        return 0;
                    }
                    else if (y == null)
                    {
                        return -1;
                    }
                    else if (x == null)
                    {
                        return 1;
                    }

                    uint xVal = Utilities.ToUInt32LittleEndian(x, i * 4);
                    uint yVal = Utilities.ToUInt32LittleEndian(y, i * 4);

                    if (xVal < yVal)
                    {
                        return -1;
                    }
                    else if (xVal > yVal)
                    {
                        return 1;
                    }
                }

                return 0;
            }
        }

        private sealed class FileNameComparer : IComparer<byte[]>
        {
            private UpperCase _stringComparer;

            public FileNameComparer(UpperCase upCase)
            {
                _stringComparer = upCase;
            }

            public int Compare(byte[] x, byte[] y)
            {
                if (x == null && y == null)
                {
                    return 0;
                }
                else if (y == null)
                {
                    return -1;
                }
                else if (x == null)
                {
                    return 1;
                }

                byte xFnLen = x[0x40];
                byte yFnLen = y[0x40];

                return _stringComparer.Compare(x, 0x42, xFnLen * 2, y, 0x42, yFnLen * 2);
            }
        }

        private sealed class SidComparer : IComparer<byte[]>
        {
            public int Compare(byte[] x, byte[] y)
            {
                if (x == null && y == null)
                {
                    return 0;
                }
                else if (y == null)
                {
                    return -1;
                }
                else if (x == null)
                {
                    return 1;
                }

                int toComp = Math.Min(x.Length, y.Length);
                for (int i = 0; i < toComp; ++i)
                {
                    int val = ((int)x[i]) - ((int)y[i]);
                    if (val != 0)
                    {
                        return val;
                    }
                }

                if (x.Length < y.Length)
                {
                    return -1;
                }
                else if (x.Length > y.Length)
                {
                    return 1;
                }

                return 0;
            }
        }
    }
}
