using UnityEngine;
using System;
using System.Security.Cryptography;
using System.Text;
using System.Linq;

[Serializable]
public class Inventory
{
	public int count, capacity;
}

[Serializable] public class InventoryDictionary : SerializableDictionary<string, Inventory> { }
[Serializable] public class VarsDictionary : SerializableDictionary<string, float> { }

public class SaveGame : ScriptableObject
{
	//--- Saved data ---------------------------
	public int version = 1;
	public string scenePath;
	public string checkPoint;
	public InventoryDictionary inventory = new InventoryDictionary();
	public VarsDictionary vars = new VarsDictionary();


	//--- Unsaved data -------------------------

	// Single savegame object currently in use.
	public static SaveGame current;

	// Damaged status:
	public bool damaged { get; private set; }

	// Slot we are in:
	public int slot { get; private set; }


	// Savegame locations:
	static string GetPath(int slot) {
		return $"{Application.persistentDataPath}/slot{slot}.save";
	}

	// Creates an empty savegame.
	public static SaveGame New(int slot) {
		Debug.Log($"Creating new save in slot {slot}.");

		SaveGame sg = ScriptableObject.CreateInstance<SaveGame>();
		sg.slot = slot;
		sg.checkPoint = "";
		sg.scenePath = "";

		return sg;
	}

	// Checks for existing saves.
	public static bool Exists(int slot) {
		string path = GetPath(slot);
		return System.IO.File.Exists(path);
	}

	// Loads a savegame, on error returns a damaged save.
	public static SaveGame Load(int slot) {
		SaveGame sg = ScriptableObject.CreateInstance<SaveGame>();
		Debug.Log($"Loading game from slot {slot}.");

		try {
			string path = GetPath(slot);
			byte[] packed = System.IO.File.ReadAllBytes(path);
			string json = Decrypt(packed);
			JsonUtility.FromJsonOverwrite(json, sg);

			if (sg.version != 1) {
				Debug.Log($"Invalid savegame version {sg.version}.");
				sg.damaged = true;
			}
		} catch (Exception e) {
			// TODO: what to do?
			Debug.LogWarning($"Error loading game: {e}");
			sg.damaged = true;
		}

		sg.slot = slot;
		return sg;
	}

	// Saves a game to its slot.
	public void Save() {
		Debug.Log($"Saving game to slot {slot}.");

		try {
			string json = JsonUtility.ToJson(this);
			string path = GetPath(slot);
			System.IO.File.WriteAllBytes(path, Encrypt(json));
		} catch (Exception e) {
			// TODO: what to do?
			Debug.LogWarning($"Error saving game: {e}");
		}
	}


	static byte[] magic = Encoding.UTF8.GetBytes("ahappylittlegame"); // 16-byte key

	// Just some simple encryption to stop people opening the file in
	// a text editor and messing around with it.
	static byte[] Encrypt(string plaintext) {
		byte[] src = Encoding.UTF8.GetBytes(plaintext);
		using (var cipher = new RijndaelManaged { Key = magic }) {
			using (var encryptor = cipher.CreateEncryptor()) {
				var ciphertext = encryptor.TransformFinalBlock(src, 0, src.Length);
				return cipher.IV.Concat(ciphertext).ToArray();
			}
		}
	}

	static string Decrypt(byte[] packed) {
		using (var cipher = new RijndaelManaged { Key = magic }) {
			int ivSize = cipher.BlockSize / 8;
			cipher.IV = packed.Take(ivSize).ToArray();
			using (var encryptor = cipher.CreateDecryptor()) {
				byte[] src = encryptor.TransformFinalBlock(packed, ivSize, packed.Length - ivSize);
				return Encoding.UTF8.GetString(src);
			}
		}
	}
}
