using UnityEngine;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;

public enum AdRewardType {
    GachaCoin   = 1,
    Avatar      = 2,
    Count,
}

[DataContract(Name="KeyValueOfintint")]
public sealed class KeyValueOfintint {
    [DataMember]
    public int Key;
    [DataMember]
    public int Value;

    public KeyValueOfintint(KeyValuePair<int, int> value){
        Key = value.Key;
        Value = value.Value;
    }
}

[DataContract]
public sealed class GameData {
    [DataMember(Name = "Data0")]
    public long beginPlayTime = DateTime.UtcNow.ToBinary();
    [DataMember(Name = "Data1")]
    public string lastPlayedVersion = Const.DefaultAppVersion;
    [DataMember(Name = "Data2")]
    public long lastPlayTime = DateTime.UtcNow.ToBinary(); // 最終プレイ日時
    [DataMember(Name = "Data3")]
    public int dailyLoginCount = 0;
    [DataMember(Name = "Data4")]
    public int adCount;
    [DataMember(Name = "Data5")]
    public int stage = 1;

    // [DataMember(Name = "Data6")]
    // public int avatarId = Const.DefaultAvatarId;
    [DataMember(Name = "Data7")] 
    private int coin;
    public int Coin
    {
        get => coin;
        set => ChangeCoin(value);
    }

    // [DataMember(Name = "Data8")]
    // private int[] avatarIdArray;
    // public List<int> avatarIdList;
    // [DataMember(Name = "Data9")]
    // public int gachaCount;
    // [DataMember(Name = "Data10")]
    // private int[] newAvatarIdArray;
    // public List<int> newAvatarIdList;
    // [DataMember(Name = "Data11")]
    // public bool isRandomAvatar;
    // CornField
    [DataMember(Name = "Data12")] 
    public List<PlantLineData> PlantLineDataList = new List<PlantLineData>();
    // [DataMember(Name = "Data13")] 
    
    [DataMember(Name = "Data14")] 
    public int MachineLevel = Const.DefaultMachineLevel;
    [DataMember(Name = "Data15")] 
    public List<SeedlingProgressData> SeedlingDataList = new List<SeedlingProgressData>();
    [DataMember(Name = "Data16")] 
    private int cornSeed;
    public int CornSeed
    {
        get => cornSeed;
        set => ChangeCornSeed(value);
    }
    // Main
    // 所持レシピ
    [DataMember(Name = "Data17")] 
    public int[] MyRecipes;
    // 所持素材
    [DataMember(Name = "Data18")] 
    public List<MaterialData> Material;
    // 店頭ポップコーン在庫
    [DataMember(Name = "Data19")]
    public List<ProductStockData> ShopStock;
    // タンクポップコーン在庫
    [DataMember(Name = "Data20")]
    public List<StorageTank> StorageTanks;
    [DataMember(Name = "Data21")]
    public int Heart = 0;
    [DataMember(Name = "Data22")]
    public int[] Pans;
    [DataMember(Name = "Data23")]
    public int ViewedShopLevel;
    [DataMember(Name = "Data24")]
    public ProductRefillMode RefillMode;
    [DataMember(Name = "Data25")]
    private int[] achievedMission;
    public List<int> AchievedMission;
    [DataMember(Name = "Data26")] 
    public int TastingCount;
    
    // BulkOrder
    [DataMember(Name = "Data27")]
    public int OrderIconIndex;
    [DataMember(Name = "Data28")]
    public int OrderIdInProgress;
    [DataMember(Name = "Data29")]
    public int OrderTextId;
    [DataMember(Name = "Data30")] 
    public int OrderConditionCount;
    [DataMember(Name = "Data31")] 
    public int CancelOrderId;
    [DataMember(Name = "Data32")]
    public int AchievedOrderLevel;
    [DataMember(Name = "Data33")]
    public List<BulkOrderResultData> CompletedProductList;
    [DataMember(Name = "Data34")] 
    private int[] deliveredOrder;
    public List<int> DeliveredOrder;
    
    // reward
    [DataMember(Name = "Data35")] 
    public long fertilizerTime = DateTime.UtcNow.ToBinary();
    [DataMember(Name = "Data36")]
    public long increaseCustomerTime= DateTime.UtcNow.ToBinary();
    [DataMember(Name = "Data37")] 
    public long kitchenRefreshTime= DateTime.UtcNow.ToBinary();
    
    // tutorial
    [DataMember(Name = "Data38")] 
    public bool isFirstPlay = true;
    
    // mission
    [DataMember(Name = "Data1001")] 
    public int TotalAddCoin { get; private set; }
    [DataMember(Name = "Data1002")] 
    public int TotalSubCoin { get; private set; }
    [DataMember(Name = "Data1003")] 
    public int TotalAddCornSeed { get; private set; }
    [DataMember(Name = "Data1004")] 
    public int TotalCustomerCount { get; private set; }
    [DataMember(Name = "Data1005")] 
    public List<ProductStockData> TotalSalesList { get; private set; }
    [DataMember(Name = "Data1006")] 
    public int BulkOrderDeliveredCount;
    public int TotalSales => TotalSalesList.Sum(data => data.Stock);
    public int RareTotalSales => TotalSalesList.Where(data => data.Rarity != ProductRarity.Normal).Sum(data => data.Stock);
    
    // daily mission
    [DataMember(Name = "Data2001")] 
    public long lastDailyMissionSetTime = DateTime.MinValue.ToBinary();
    [DataMember(Name = "Data2002")] 
    public int[] dailyMissionIdArray;
    [DataMember(Name = "Data2003")] 
    public int[] dailyMissionAchievedIdArray;
    [DataMember(Name = "Data2004")] 
    public bool dailyMissionCompleted;
    
    // [DataMember(Name = "Data2100")]
    [DataMember(Name = "Data2101")] 
    public int DailyTotalAddCoin { get; private set; }
    [DataMember(Name = "Data2102")] 
    public int DailyTotalSubCoin { get; private set; }
    [DataMember(Name = "Data2103")] 
    public int DailyTotalAddCornSeed { get; private set; }
    [DataMember(Name = "Data2104")] 
    public int DailyTotalCustomerCount { get; private set; }
    [DataMember(Name = "Data2105")] 
    public List<ProductStockData> DailyTotalSalesList { get; private set; }
    [DataMember(Name = "Data2106")] 
    public int DailyRecipeGetCount;
    [DataMember(Name = "Data2107")] 
    public int DailyBulkOrderDeliveredCount;
    public int DailyTotalSales => DailyTotalSalesList.Sum(data => data.Stock);
    public int DailyRareTotalSales => DailyTotalSalesList.Where(data => data.Rarity != ProductRarity.Normal).Sum(data => data.Stock);

    private void ChangeCornSeed(int value)
    {
        if (cornSeed < value)
        {
            TotalAddCornSeed += value - cornSeed;
            DailyTotalAddCornSeed += value - cornSeed;
        }
        cornSeed = value;
    }

    private void ChangeCoin(int value)
    {
        // Debug.Log($"{coin} {value}");
        if (coin < value)
        {
            TotalAddCoin += value - coin;
            DailyTotalAddCoin += value - coin;
        }
        else
        {
            TotalSubCoin += coin - value;
            DailyTotalSubCoin += coin - value;
        }
        coin = value;
    }

    public void AddCustomerCount(int value)
    {
        TotalCustomerCount += value;
        DailyTotalCustomerCount += value;
    }

    public void AddSalesCount(int id, int stock, ProductRarity rarity)
    {
        OrderConditionCount += stock;
        if (!(TotalSalesList.FirstOrDefault(x => x.FlavorId == id && x.Rarity == rarity) is ProductStockData stockData))
        {
            stockData = new ProductStockData {FlavorId = id, Rarity = rarity, Stock = 0};
            TotalSalesList.Add(stockData);
        }
        stockData.Stock += stock;

        if (!(DailyTotalSalesList.FirstOrDefault(x => x.FlavorId == id && x.Rarity == rarity) is ProductStockData dailyStockData))
        {
            dailyStockData = new ProductStockData {FlavorId = id, Rarity = rarity, Stock = 0};
            DailyTotalSalesList.Add(dailyStockData);
        }
        dailyStockData.Stock += stock;
    }

    public int GetTotalSalesByFlavor(int id)
    {
        return TotalSalesList.Where(data => data.FlavorId == id).Sum(data => data.Stock);
    }
    
    public int GetTotalSalesByFlavorWithRare(int id)
    {
        return TotalSalesList.Where(data => data.FlavorId == id && data.Rarity != ProductRarity.Normal).Sum(data => data.Stock);
    }
    
    public int GetDailyTotalSalesByFlavor(int id)
    {
        return DailyTotalSalesList.Where(data => data.FlavorId == id).Sum(data => data.Stock);
    }
    
    public int GetDailyTotalSalesByFlavorWithRare(int id)
    {
        return DailyTotalSalesList.Where(data => data.FlavorId == id && data.Rarity != ProductRarity.Normal).Sum(data => data.Stock);
    }

    public void ResetNormalTotalCount()
    {
        TotalAddCoin = 0;
        TotalSubCoin = 0;
        TotalAddCornSeed = 0;
        TotalCustomerCount = 0;
        TotalSalesList.Clear();
        BulkOrderDeliveredCount = 0;
    }

    public void ResetDailyTotalCount()
    {
        DailyTotalAddCoin = 0;
        DailyTotalSubCoin = 0;
        DailyTotalAddCornSeed = 0;
        DailyTotalCustomerCount = 0;
        DailyTotalSalesList.Clear();
        DailyRecipeGetCount = 0;
        DailyBulkOrderDeliveredCount = 0;
    }
    
    public void AddTastingCustomerCount(int value)
    {
        TastingCount += Mathf.FloorToInt(value / 2f);
    }

    public TimeSpan GetDailyMissionLimitTime()
    {
        return DateTime.FromBinary(lastDailyMissionSetTime).ToLocalTime().AddHours(Const.DailyMissionResetOffset).Date
            .AddDays(1).AddHours(-Const.DailyMissionResetOffset)
            .Subtract(DateTime.UtcNow.ToLocalTime());
    }

    public void FinishTutorial()
    {
        isFirstPlay = false;
        fertilizerTime = DateTime.UtcNow.ToBinary();
        increaseCustomerTime = DateTime.UtcNow.ToBinary();
        kitchenRefreshTime = DateTime.UtcNow.ToBinary();
    }

    // public void ChangeAvatar(AvatarData avatarData){
    //     newAvatarIdList.Remove(avatarData.id);
    // }
    // public void AddAvatar(AvatarData avatarData){
    //     if(HasAvatar(avatarData))    return ;
    //     avatarIdList.Add(avatarData.id);
    //     newAvatarIdList.Add(avatarData.id);
    // }
    // public bool HasAvatar(AvatarData avatarData){
    //     return avatarIdList.Exists(id => id == avatarData.id);
    // }
    // public void SetRandom(bool isRandom, AvatarCategory category){
    // }
    // public bool GetRandom(AvatarCategory category){
    //     return false;
    // }



//    [OnDeserialized] Unityだと機能しない
    public void OnDeserialized(){
        PreDeserialize();

        // if(avatarIdList.Count < 1){
        //     avatarIdList.Add(Const.DefaultAvatarId);
        // }
        if (MyRecipes.Length < 1)
        {
            MyRecipes = new[] {Const.DefaultRecipeId};
        }
        if (StorageTanks.Count < 1)
        {
            StorageTanks.Add(new StorageTank
                {
                    Capacity = Const.TankCapacity,
                    FlavorId = -1,
                }
            );
        }
        if (Pans.Length < 1)
        {
            Pans = new[] {Const.DefaultPanId};
        }
        
        // 追加した要素の初期化用
        // TODO Release前にまっさらにする
    }
    public void PreDeserialize(){
        // avatarIdList = avatarIdArray == null ? new List<int>() : avatarIdArray.ToList();
        // newAvatarIdList = newAvatarIdArray == null ? new List<int>() : newAvatarIdArray.ToList();
        // lastAdRewardTimeList = lastAdRewardTimeArray == null ? new List<long>() : lastAdRewardTimeArray.ToList();
        MyRecipes = MyRecipes ?? new int[0];
        Material = Material ?? new List<MaterialData>();
        ShopStock = ShopStock ?? new List<ProductStockData>();
        StorageTanks = StorageTanks ?? new List<StorageTank>();
        Pans = Pans ?? new int[0];
        TotalSalesList = TotalSalesList ?? new List<ProductStockData>();
        AchievedMission = achievedMission?.ToList() ?? new List<int>();
        CompletedProductList = CompletedProductList ?? new List<BulkOrderResultData>();
        DeliveredOrder = deliveredOrder?.ToList() ?? new List<int>();
        dailyMissionIdArray = dailyMissionIdArray ?? new int[0];
        dailyMissionAchievedIdArray = dailyMissionAchievedIdArray ?? new int[0];
        DailyTotalSalesList = DailyTotalSalesList ?? new List<ProductStockData>();
    }
    private Dictionary<int, int> ArrayToDictionary(KeyValueOfintint[] array){
        var dictionary = new Dictionary<int, int>();
        if(array != null && array.Length > 0){
            foreach(var kv in array){
                dictionary.Add(kv.Key, kv.Value);
            }
        }
        return dictionary;
    }
    public void PreSerialize(){
        // avatarIdArray = avatarIdList.ToArray();
        // newAvatarIdArray = newAvatarIdList.ToArray();
        // lastAdRewardTimeArray = lastAdRewardTimeList.ToArray();
        achievedMission = AchievedMission.ToArray();
        deliveredOrder = DeliveredOrder.ToArray();
    }
    private KeyValueOfintint[] DictionaryToArray(Dictionary<int, int> dictionary){
        var array = new KeyValueOfintint[dictionary.Count];
        int index = 0;
        foreach(var kvp in dictionary){
            array[index++] = new KeyValueOfintint(kvp);
        }
        return array;
    }
}

public static class GameDataManager {

    public enum LoadState {
        NoFile,
        ExistsClashed,
        DeserializeFailed,
        Old,
        Succeeded,
    }

//    private static readonly int DiskFullTextId = 201;
//    private static readonly int UnauthorizedAccessExceptionTextId = 202;

    private static GameData dataCache = null;
    public static GameData GameData {
        get{ return dataCache ?? (dataCache = LoadGameData()); }
    }
    public static bool IsLoaded {
        get{ return dataCache != null; }
    }
    public static bool ExistsGameData(){
        return UsayaStorageManager.Exists(UsayaStorageFilename.Main_Data, Const.GameDataTag) || UsayaStorageManager.ExistsBackup(UsayaStorageFilename.Main_Data, Const.GameDataTag);
    }
    private static GameData LoadGameData(){
        GameData data = null;
        var loadState = LoadState.ExistsClashed;
        try{
            if(UsayaStorageManager.Exists(UsayaStorageFilename.Main_Data, Const.GameDataTag)){
                loadState = LoadState.DeserializeFailed;
                data = StringToGameData(UsayaStorageManager.Load<string>(UsayaStorageFilename.Main_Data, Const.GameDataTag));
                loadState = LoadState.Succeeded;
            }else{
                loadState = LoadState.NoFile;
            }
        }catch(Exception){
        }finally{
            var backup = LoadGameDataBackup(loadState);
            if(loadState != LoadState.Succeeded || (data.stage < backup.stage)){
                data = backup;
                if(loadState == LoadState.Succeeded){
//                    AnalyticsManager.DataLoadWarning(LoadState.Old);
                }
            }
        }
        data.OnDeserialized();

        if(Application.version.CompareVersion(data.lastPlayedVersion) != 0){
            data.lastPlayedVersion = Application.version;
        }
        return data;
    }
    private static GameData LoadGameDataBackup(LoadState mainLoadState){
        GameData result = null;
        var loadState = LoadState.ExistsClashed;
        try{
            if(UsayaStorageManager.ExistsBackup(UsayaStorageFilename.Main_Data, Const.GameDataTag)){
                loadState = LoadState.DeserializeFailed;
                result = StringToGameData(UsayaStorageManager.LoadBackup(UsayaStorageFilename.Main_Data, Const.GameDataTag));
                loadState = LoadState.Succeeded;
                if(mainLoadState != LoadState.Succeeded){
//                    AnalyticsManager.DataLoadWarning(mainLoadState);
                }
            }else{
                loadState = LoadState.NoFile;
            }
        }catch(Exception){
        }finally{
            if(mainLoadState != LoadState.Succeeded && loadState != LoadState.NoFile){
//                AnalyticsManager.DataLoadWarning(mainLoadState, loadState);
            }
            if(loadState != LoadState.Succeeded){
                result = CreateData();
            }
        }
        return result;
    }

    private static GameData CreateData(){
        var data = new GameData();
        data.lastPlayedVersion = Application.version;
        return data;
    }
    public static bool OverWriteGameData(string dataString){
        try{
            dataCache = StringToGameData(dataString);
            return true;
        }catch{
            return false;
        }
    }
    public static void SaveGameData(){
        string data = GameDataToString(GameData);
        bool isMainSaved = false;
        Exception exceptionMain = null;
        try{
            UsayaStorageManager.Save<string>(UsayaStorageFilename.Main_Data, Const.GameDataTag, data);
            isMainSaved = true;
        }catch(Exception e){
            // TODO ログサーバが出来たらExceptionを送りたい
            exceptionMain = e;
        }finally{
            bool isBackupSaved = false;
            Exception exceptionBackup = null;
            try{
                UsayaStorageManager.SaveBackup(UsayaStorageFilename.Main_Data, Const.GameDataTag, data);
                isBackupSaved = true;
            }catch(Exception e){
                // TODO ログサーバが出来たらExceptionを送りたい
                exceptionBackup = e;
            }finally{
                if(!isMainSaved && !isBackupSaved){
//                    AnalyticsManager.DataSaveWarning("GameData", SaveState.MainAndBackupFailed);
//                    SaveFailedDialogManager.ShowDialog(exceptionMain, exceptionBackup);
                }else if(!isMainSaved){
//                    AnalyticsManager.DataSaveWarning("GameData", SaveState.MainFailed);
                }else if(!isBackupSaved){
//                    AnalyticsManager.DataSaveWarning("GameData", SaveState.BackupFailed);
                }
            }
        }
    }
    public static string GetGameDataString(){
        return GameDataToString(GameData);
    }
    private static string GameDataToString(GameData data){
        data.PreSerialize();
        return AvoEx.AesEncryptor.Encrypt(
            StringExtensions.DataToXML<GameData>(data)
            .Replace("KeyValueOfintint", "KVOii")
            .Replace("KeyValueOfintlong", "KVOil")
            .Replace("KeyValueOfintInfiniteFloat", "KVOiIF")
//            .Compression()
        );
    }
    private static GameData StringToGameData(string str){
        str = AvoEx.AesEncryptor.DecryptString(str)
//            .DeCompression()
            .Replace("KVOii", "KeyValueOfintint")
            .Replace("KVOil", "KeyValueOfintlong")
            .Replace("KVOiIF", "KeyValueOfintInfiniteFloat");
        return StringExtensions.XMLToData<GameData>(str);
    }

#if UNITY_EDITOR || DEVELOPMENT_BUILD
    public static void ResetData(){
        dataCache = CreateData();
        dataCache.OnDeserialized();
    }
#endif
}
