using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using UnityEngine;

public enum UsayaStorageFilename {
    Main_Data,
    Settings_Data,
    Wallet_Data,
    Purchaser_Data,
}

public class UsayaStorageManager {

    [Serializable]
    protected class DataSet {
        public string tag;
        public string data;

        public static DataSet CreateInstance<T>(string tag, T value){
            DataSet dataSet = new DataSet();
            dataSet.Save(tag, value);
            return dataSet;
        }
        public void Save<T>(string tag, T value){
            this.tag = tag;
            this.data = JsonUtility.ToJson(new Serialization<T>(new List<T>{ value }));
        }
        public T Load<T>(){
            return JsonUtility.FromJson<Serialization<T>>(data).ToList()[0];
        }
    }
    private const string FileExtension = ".usaya";

    private static Dictionary<string, List<DataSet>> dataSetDictionary = new Dictionary<string, List<DataSet>>();

    private static string ToPath(UsayaStorageFilename filename){
        return Application.persistentDataPath + "/" + filename.ToString() + FileExtension;
    }

    public static void Reset(){
        dataSetDictionary.Clear();
    }
    private static List<DataSet> GetDataSetList(UsayaStorageFilename filename){
        var key = filename.ToString();
        if(dataSetDictionary.ContainsKey(key)){
            return dataSetDictionary[key];
        }else{
            try{
                List<DataSet> list;
                if(Exists(filename)){
                    list = LoadList(filename);
                }else{
                    list = new List<DataSet>();
                }
                dataSetDictionary[key] = list;
                return list;
            }catch(Exception e){
                dataSetDictionary[key] = new List<DataSet>();
                if(filename == UsayaStorageFilename.Settings_Data){
                    return dataSetDictionary[key];
                }else{
                    throw e;
                }
            }
        }
    }
    private static List<DataSet> SaveDataSet<T>(UsayaStorageFilename filename, string tag, T value){
        var dataSetList = GetDataSetList(filename);
        bool isExists = false;
        foreach(var dataSet in dataSetList){
            if(dataSet.tag == tag){
                dataSet.Save(tag, value);
                isExists = true;
                break;
            }
        }
        if(!isExists){
            dataSetList.Add(DataSet.CreateInstance(tag, value));
        }
        return dataSetList;
    }
    private static DataSet LoadDataSet(UsayaStorageFilename filename, string tag){
        var dataSetList = GetDataSetList(filename);
        foreach(var dataSet in dataSetList){
            if(dataSet.tag == tag) return dataSet;
        }
        return null;
    }
    private static bool ExistsDataSet(UsayaStorageFilename filename, string tag){
        var dataSetList = GetDataSetList(filename);
        foreach(var dataSet in dataSetList){
            if(dataSet.tag == tag) return true;
        }
        return false;
    }

    public static void Save<T>(UsayaStorageFilename filename, string tag, T value){
        var list = SaveDataSet(filename, tag, value);
        SaveList(filename, list);
    }
    private static void SaveList(UsayaStorageFilename filename, List<DataSet> list){
        var path = ToPath(filename);
        var json = JsonUtility.ToJson(new Serialization<DataSet>(list));
        using(StreamWriter writer = new StreamWriter(path, false)){
            writer.Write(json);
            writer.Flush();
            writer.Close();
        }
    }

    public static T LoadOrDefault<T>(UsayaStorageFilename filename, string tag, T defaultValue){
        if(Exists(filename, tag)){
            return Load<T>(filename, tag);
        }else{
            return defaultValue;
        }
    }
    public static T Load<T>(UsayaStorageFilename filename, string tag){
        var dataSet = LoadDataSet(filename, tag);
        return dataSet.Load<T>();
    }
    private static List<DataSet> LoadList(UsayaStorageFilename filename){
        var path = ToPath(filename);
        var fileInfo = new FileInfo(path);
        string json = LoadJson(fileInfo);
        var result = Deserialize<Serialization<DataSet>>(json);
        return SerializationToList(result);
    }
    private static string LoadJson(FileInfo fileInfo){
        string json;
        using(var fileStream = fileInfo.OpenRead()){
            using(var streamReader = new StreamReader(fileStream, Encoding.UTF8)){
                json = streamReader.ReadToEnd();
                streamReader.Close();
            }
        }
        return json;
    }
    private static T Deserialize<T>(string json){
        return JsonUtility.FromJson<T>(json);
    }
    private static List<T> SerializationToList<T>(Serialization<T> serialization){
        return serialization.ToList();
    }

    public static bool Exists(UsayaStorageFilename filename, string tag){
        return ExistsDataSet(filename, tag);
    }
    public static bool Exists(UsayaStorageFilename filename){
        var path = ToPath(filename);
        return File.Exists(path);
    }
    
    public static void Remove(UsayaStorageFilename filename, string tag){
        var list = GetDataSetList(filename);
        DataSet result = null;
        foreach(var dataSet in list){
            if(dataSet.tag == tag){
                result = dataSet;
                break;
            }
        }
        if(result != null){
            list.Remove(result);
            SaveList(filename, list);
        }
    }


    public static void SaveAndBackup(UsayaStorageFilename filename, string tag, string value){
//        bool isMainSaved = false;
        try{
            Save<string>(filename, tag, value);
//            isMainSaved = true;
        }catch(Exception){
            // TODO ログサーバが出来たらExceptionを送りたい
        }finally{
            SaveBackup(filename, tag, value);
        }
    }
    public static void SaveBackup(UsayaStorageFilename filename, string tag, string value){
        string key = string.Format("{0}?tag={1}", filename.ToString(), tag);
        PlayerPrefs.SetString(key, value);
        PlayerPrefs.Save();
    }

    public static bool ExistsBackup(UsayaStorageFilename filename, string tag){
        string key = string.Format("{0}?tag={1}", filename.ToString(), tag);
        return PlayerPrefs.HasKey(key);
    }

    public static string LoadBackup(UsayaStorageFilename filename, string tag){
        string key = string.Format("{0}?tag={1}", filename.ToString(), tag);
        return PlayerPrefs.GetString(key);
    }

    public static void IsDiskFull(Exception ex, Func<string> getDiskFullMessage){
        const int ERROR_HANDLE_DISK_FULL = 0x27;
        const int ERROR_DISK_FULL = 0x70;

        int errorCode = Marshal.GetHRForException(ex) & 0xFFFF;
        if(errorCode == ERROR_HANDLE_DISK_FULL || errorCode == ERROR_DISK_FULL){
            NativeUtils.ShowAlert(ex.Message, getDiskFullMessage());
        }else{
            throw ex;
        }
    }

    public static string LoadRawString(UsayaStorageFilename filename){
        var path = ToPath(filename);
        var fileInfo = new FileInfo(path);
        string json;
        using(var streamReader = new StreamReader(fileInfo.OpenRead(), Encoding.UTF8)){
            json = streamReader.ReadToEnd();
            streamReader.Close();
        }
        return json;
    }
    public static void SaveRawString(UsayaStorageFilename filename, string str){
        var path = ToPath(filename);
        using(StreamWriter writer = new StreamWriter(path, false)){
            writer.Write(str);
            writer.Flush();
            writer.Close();
        }
    }
}
