/*
 * Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

using Framework.Constants;
using Framework.Database;
using Framework.GameMath;
using Framework.IO;
using System;
using System.Collections.Generic;
using System.Reflection;

namespace Game.DataStorage
{
    public interface IDB2Storage
    {
        bool HasRecord(uint id);

        void WriteRecord(uint id, LocaleConstant locale, ByteBuffer buffer);

        void EraseRecord(uint id);
    }

    [Serializable]
    public class DB6Storage<T> : Dictionary<uint, T>, IDB2Storage where T : new()
    {
        public void LoadData(uint indexField, DBClientHelper[] helpers, HotfixStatements preparedStatement, HotfixStatements preparedStatementLocale)
        {
            SQLResult result = DB.Hotfix.Query(DB.Hotfix.GetPreparedStatement(preparedStatement));
            if (!result.IsEmpty())
            {
                do
                {
                    var idValue = result.Read<uint>((int)indexField);

                    var obj = new T();
                    int index = 0;
                    for (var fieldIndex = 0; fieldIndex < helpers.Length; fieldIndex++)
                    {
                        var helper = helpers[fieldIndex];
                        if (helper.IsArray)
                        {
                            Array array = (Array)helper.Getter(obj);
                            for (var i = 0; i < array.Length; ++i)
                            {
                                switch (Type.GetTypeCode(helper.RealType))
                                {
                                    case TypeCode.SByte:
                                        helper.SetValue(array, result.Read<sbyte>(index++), i);
                                        break;
                                    case TypeCode.Byte:
                                        helper.SetValue(array, result.Read<byte>(index++), i);
                                        break;
                                    case TypeCode.Int16:
                                        helper.SetValue(array, result.Read<short>(index++), i);
                                        break;
                                    case TypeCode.UInt16:
                                        helper.SetValue(array, result.Read<ushort>(index++), i);
                                        break;
                                    case TypeCode.Int32:
                                        helper.SetValue(array, result.Read<int>(index++), i);
                                        break;
                                    case TypeCode.UInt32:
                                        helper.SetValue(array, result.Read<uint>(index++), i);
                                        break;
                                    case TypeCode.Single:
                                        helper.SetValue(array, result.Read<float>(index++), i);
                                        break;
                                    case TypeCode.String:
                                        helper.SetValue(array, result.Read<string>(index++), i);
                                        break;
                                    case TypeCode.Object:
                                        switch (helper.FieldType.Name)
                                        {
                                            case "Vector2":
                                                var vector2 = new Vector2();
                                                vector2.X = result.Read<float>(index++);
                                                vector2.Y = result.Read<float>(index++);
                                                helper.SetValue(array, vector2, i);
                                                break;
                                            case "Vector3":
                                                var vector3 = new Vector3();
                                                vector3.X = result.Read<float>(index++);
                                                vector3.Y = result.Read<float>(index++);
                                                vector3.Z = result.Read<float>(index++);
                                                helper.SetValue(array, vector3, i);
                                                break;
                                            case "LocalizedString":
                                                LocalizedString locString = new LocalizedString();
                                                locString[Global.WorldMgr.GetDefaultDbcLocale()] = result.Read<string>(index++);
                                                helper.SetValue(array, locString, i);
                                                break;
                                            default:
                                                Log.outError(LogFilter.ServerLoading, "Wrong Array Type: {0}", helper.FieldType.Name);
                                                break;
                                        }
                                        break;
                                    default:
                                        Log.outError(LogFilter.ServerLoading, "Wrong Array Type: {0}", helper.FieldType.Name);
                                        break;
                                }
                            }
                        }
                        else
                        {
                            switch (Type.GetTypeCode(helper.RealType))
                            {
                                case TypeCode.SByte:
                                    helper.SetValue(obj, result.Read<sbyte>(index++));
                                    break;
                                case TypeCode.Byte:
                                    helper.SetValue(obj, result.Read<byte>(index++));
                                    break;
                                case TypeCode.Int16:
                                    helper.SetValue(obj, result.Read<short>(index++));
                                    break;
                                case TypeCode.UInt16:
                                    helper.SetValue(obj, result.Read<ushort>(index++));
                                    break;
                                case TypeCode.Int32:
                                    helper.SetValue(obj, result.Read<int>(index++));
                                    break;
                                case TypeCode.UInt32:
                                    helper.SetValue(obj, result.Read<uint>(index++));
                                    break;
                                case TypeCode.Single:
                                    helper.SetValue(obj, result.Read<float>(index++));
                                    break;
                                case TypeCode.String:
                                    string str = result.Read<string>(index++);
                                    helper.SetValue(obj, str);
                                    break;
                                case TypeCode.Object:
                                    switch (helper.FieldType.Name)
                                    {
                                        case "Vector2":
                                            var vector2 = new Vector2();
                                            vector2.X = result.Read<float>(index++);
                                            vector2.Y = result.Read<float>(index++);
                                            helper.SetValue(obj, vector2);
                                            break;
                                        case "Vector3":
                                            var vector3 = new Vector3();
                                            vector3.X = result.Read<float>(index++);
                                            vector3.Y = result.Read<float>(index++);
                                            vector3.Z = result.Read<float>(index++);
                                            helper.SetValue(obj, vector3);
                                            break;
                                        case "LocalizedString":
                                            LocalizedString locString = new LocalizedString();
                                            locString[Global.WorldMgr.GetDefaultDbcLocale()] = result.Read<string>(index++);
                                            helper.SetValue(obj, locString);
                                            break;
                                        default:
                                            Log.outError(LogFilter.ServerLoading, "Wrong Array Type: {0}", helper.FieldType.Name);
                                            break;
                                    }
                                    break;
                                default:
                                    Log.outError(LogFilter.ServerLoading, "Wrong Array Type: {0}", helper.FieldType.Name);
                                    break;
                            }
                        }
                    }

                    base[idValue] = obj;
                }
                while (result.NextRow());
            }

            if (preparedStatementLocale == 0)
                return;

            for (LocaleConstant locale = 0; locale < LocaleConstant.OldTotal; ++locale)
            {
                if (Global.WorldMgr.GetDefaultDbcLocale() == locale || locale == LocaleConstant.None)
                    continue;

                PreparedStatement stmt = DB.Hotfix.GetPreparedStatement(preparedStatementLocale);
                stmt.AddValue(0, locale.ToString());
                SQLResult localeResult = DB.Hotfix.Query(stmt);
                if (localeResult.IsEmpty())
                    continue;

                do
                {
                    int index = 0;
                    var obj = this.LookupByKey(localeResult.Read<uint>(index++));
                    if (obj == null)
                        continue;

                    for (var i = 0; i < helpers.Length; i++)
                    {
                        var fieldInfo = helpers[i];
                        if (fieldInfo.FieldType != typeof(LocalizedString))
                            continue;

                        LocalizedString locString = (LocalizedString)fieldInfo.Getter(obj);
                        locString[locale] = localeResult.Read<string>(index++);
                    }
                } while (localeResult.NextRow());
            }
        }

        public bool HasRecord(uint id)
        {
            return ContainsKey(id);
        }

        public void WriteRecord(uint id, LocaleConstant locale, ByteBuffer buffer)
        {
            T entry = this.LookupByKey(id);

            foreach (var fieldInfo in entry.GetType().GetFields())
            {
                if (fieldInfo.Name == "Id")
                    continue;

                var type = fieldInfo.FieldType;
                if (type.IsArray)
                {
                    WriteArrayValues(entry, fieldInfo, buffer);
                    continue;
                }

                switch (Type.GetTypeCode(type))
                {
                    case TypeCode.Boolean:
                        buffer.WriteUInt8(fieldInfo.GetValue(entry));
                        break;
                    case TypeCode.SByte:
                        buffer.WriteInt8(fieldInfo.GetValue(entry));
                        break;
                    case TypeCode.Byte:
                        buffer.WriteUInt8(fieldInfo.GetValue(entry));
                        break;
                    case TypeCode.Int16:
                        buffer.WriteInt16(fieldInfo.GetValue(entry));
                        break;
                    case TypeCode.UInt16:
                        buffer.WriteUInt16(fieldInfo.GetValue(entry));
                        break;
                    case TypeCode.Int32:
                        buffer.WriteInt32(fieldInfo.GetValue(entry));
                        break;
                    case TypeCode.UInt32:
                        buffer.WriteUInt32(fieldInfo.GetValue(entry));
                        break;
                    case TypeCode.Int64:
                        buffer.WriteInt64(fieldInfo.GetValue(entry));
                        break;
                    case TypeCode.UInt64:
                        buffer.WriteUInt64(fieldInfo.GetValue(entry));
                        break;
                    case TypeCode.Single:
                        buffer.WriteFloat(fieldInfo.GetValue(entry));
                        break;
                    case TypeCode.Object:
                        switch (type.Name)
                        {
                            case "LocalizedString":
                                LocalizedString locStr = (LocalizedString)fieldInfo.GetValue(entry);
                                if (!locStr.HasString(locale))
                                {
                                    locale = 0;
                                    if (!locStr.HasString(locale))
                                    {
                                        buffer.WriteUInt16(0);
                                        break;
                                    }
                                }

                                string str = locStr[locale];
                                buffer.WriteUInt16(str.Length + 1);
                                buffer.WriteCString(str);
                                break;
                        }
                        break;
                }
            }
        }

        void WriteArrayValues(object entry, FieldInfo fieldInfo, ByteBuffer buffer)
        {
            var type = fieldInfo.FieldType.GetElementType();
            var length = ((Array)fieldInfo.GetValue(entry)).Length;
            for (var i = 0; i < length; ++i)
            {
                var value = (Array)fieldInfo.GetValue(entry);
                switch (Type.GetTypeCode(type))
                {
                    case TypeCode.Boolean:
                        buffer.WriteUInt8(value.GetValue(i));
                        break;
                    case TypeCode.SByte:
                        buffer.WriteInt8(value.GetValue(i));
                        break;
                    case TypeCode.Byte:
                        buffer.WriteUInt8(value.GetValue(i));
                        break;
                    case TypeCode.Int16:
                        buffer.WriteInt16(value.GetValue(i));
                        break;
                    case TypeCode.UInt16:
                        buffer.WriteUInt16(value.GetValue(i));
                        break;
                    case TypeCode.Int32:
                        buffer.WriteInt32(value.GetValue(i));
                        break;
                    case TypeCode.UInt32:
                        buffer.WriteUInt32(value.GetValue(i));
                        break;
                    case TypeCode.Int64:
                        buffer.WriteInt64(value.GetValue(i));
                        break;
                    case TypeCode.UInt64:
                        buffer.WriteUInt64(value.GetValue(i));
                        break;
                    case TypeCode.Single:
                        buffer.WriteFloat(value.GetValue(i));
                        break;
                    case TypeCode.String:
                        var str = (string)value.GetValue(i);
                        buffer.WriteInt32(str.Length);
                        buffer.WriteString(str);
                        break;
                    case TypeCode.Object:
                        switch (type.Name)
                        {
                            case "Unused":
                                buffer.WriteUInt32(0u);
                                break;
                        }
                        break;
                }
            }
        }

        public void EraseRecord(uint id)
        {
            Remove(id);
        }

        public uint tableHash;
    }
}
