// Copyright © 2017-2018 Atomic Software, LLC. All Rights Reserved.
// See LICENSE.md for full license information.

using System;
using System.Data;
using System.IO;

namespace Atom.Core.IO
{
    public class SHNFile : DataTable
    {
        public uint ColumnCount { get; private set; }
        public uint RowCount { get; private set; }
        public string FileName { get; }
        public uint Header { get; private set; }
        public byte[] CryptHeader { get; private set; }

        private uint DefaultRowLength;

        public SHNFile(string filePath)
        {
            FileName = filePath;
            TableName = Path.GetFileName(filePath);

            Read();
        }

        private void Read()
        {
            if (!File.Exists(FileName))
            {
                throw new FileNotFoundException("The file could not be found", FileName);
            }

            byte[] Bytes;

            using (var FileHandle = File.Open(FileName, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                using (var Reader = new BinaryReader(FileHandle))
                {
                    CryptHeader = Reader.ReadBytes(32);

                    var Length = Reader.ReadInt32() - 36;
                    Bytes = Reader.ReadBytes(Length);

                    var Xor = (byte)Length;

                    for (var i = Length - 1; i >= 0; i--)
                    {
                        Bytes[i] = (byte)(Bytes[i] ^ Xor);

                        var XorNext = (byte)i;
                        XorNext = (byte)(XorNext & 0xF);
                        XorNext = (byte)(XorNext + 0x55);
                        XorNext = (byte)(XorNext ^ (byte)((byte)i * 0xB));
                        XorNext = (byte)(XorNext ^ Xor);
                        XorNext = (byte)(XorNext ^ 0xAA);

                        Xor = XorNext;
                    }
                }
            }

            using (var Stream = new MemoryStream(Bytes))
            {
                using (var Reader = new SHNDataReader(Stream))
                {
                    Header = Reader.ReadUInt32();
                    RowCount = Reader.ReadUInt32();
                    DefaultRowLength = Reader.ReadUInt32();
                    ColumnCount = Reader.ReadUInt32();

                    /* LOAD COLUMNS */
                    var UnknownColumnCount = 0;
                    var Length = 2;

                    for (var i = 0; i < ColumnCount; i++)
                    {
                        var NewColumn = new SHNColumn(Reader, ref UnknownColumnCount);
                        Length += NewColumn.Length;

                        Columns.Add(NewColumn);
                    }

                    if (Length != DefaultRowLength)
                    {
                        throw new Exception("The default row length does not fit");
                    }

                    /* LOAD ROWS */
                    var NewRow = new object[ColumnCount];

                    for (uint i = 0; i < RowCount; i++)
                    {
                        uint RowLength = Reader.ReadUInt16();

                        for (var x = 0; x < ColumnCount; x++)
                        {
                            var RowColumn = (SHNColumn)Columns[x];

                            switch (RowColumn.Type)
                            {
                                case 1:
                                case 12:
                                case 16:
                                    NewRow[x] = Reader.ReadByte();
                                    break;
                                case 2:
                                    NewRow[x] = Reader.ReadUInt16();
                                    break;
                                case 3:
                                case 11:
                                case 18:
                                case 27:
                                    NewRow[x] = Reader.ReadUInt32();
                                    break;
                                case 5:
                                    NewRow[x] = Reader.ReadSingle();
                                    break;
                                case 9:
                                case 24:
                                    NewRow[x] = Reader.ReadPaddedString(RowColumn.Length);
                                    break;
                                case 13:
                                case 21:
                                    NewRow[x] = Reader.ReadInt16();
                                    break;
                                case 20:
                                    NewRow[x] = Reader.ReadSByte();
                                    break;
                                case 22:
                                    NewRow[x] = Reader.ReadInt32();
                                    break;
                                case 26:
                                    NewRow[x] = Reader.ReadPaddedString((int)(RowLength - DefaultRowLength + 1));
                                    break;
                                default:
                                    throw new Exception("New SHN column type found");
                            }
                        }

                        Rows.Add(NewRow);
                    }
                }
            }
        }
    }
}
