using Cryville.Audio;
using Cryville.Audio.Source;
using Cryville.Common;
using Cryville.Common.Unity;
using Cryville.Common.Unity.Input;
using FFmpeg.AutoGen;
using Ionic.Zip;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using Logger = Cryville.Common.Logger;

#if UNITY_ANDROID
using AOT;
#endif

namespace Cryville.Crtr {
	public static class Game {
		public static string GameDataPath {
			get;
			private set;
		}
		public readonly static string FileProtocolPrefix
#if UNITY_STANDALONE_WIN
			= "file:///";
#elif UNITY_ANDROID
			= "file://";
#else
#error No file protocol prefix is defined.
#endif
		public static IAudioDeviceManager AudioManager;
		public static AudioClient AudioClient;
		public static SimpleSequencerSource AudioSequencer;
		public static SimpleSequencerSession AudioSession;
		public static InputManager InputManager;
		public readonly static NetworkTaskWorker NetworkTaskWorker = new NetworkTaskWorker();

		public readonly static JsonSerializerSettings GlobalJsonSerializerSettings
			= new JsonSerializerSettings() {
				DefaultValueHandling = DefaultValueHandling.Ignore,
			};

		public static BufferedLogger MainLogger { get; private set; }

		static bool _init;
		public static void Init() {
			if (_init) return;
			_init = true;

			bool _bcflag = new Version(Settings.Default.LastRunVersion) < new Version("0.4");
			if (_bcflag) Settings.Default.Reset();

			Logger.SetLogPath(Settings.Default.GameDataPath + "/logs");
			MainLogger = new BufferedLogger();
			Application.logMessageReceivedThreaded += OnLog;
			Logger.Create("main", MainLogger);

			if (_bcflag) Logger.Log("main", 2, "Game", "Reset all settings");
			
			GameDataPath = Settings.Default.GameDataPath;

			Input.simulateMouseWithTouches = false;
			InputManager = new InputManager();

#if UNITY_EDITOR_WIN
			ffmpeg.RootPath = Application.dataPath + "/Plugins/Windows";
#elif UNITY_STANDALONE_WIN
			ffmpeg.RootPath = Application.dataPath + "/Plugins/x86_64";
#elif UNITY_ANDROID
			ffmpeg.RootPath = "";
#else
#error No FFmpeg search path.
#endif
#if UNITY_ANDROID
			Cryville.Audio.OpenSL.OutputClient.CallbackFunction = audioCallback;
#endif
			AudioManager = EngineBuilder.Create();
			Logger.Log("main", 1, "Audio", "Using audio API: {0}", AudioManager.GetType().Namespace);
			AudioClient = AudioManager.GetDefaultDevice(DataFlow.Out).Connect();
			AudioClient.Init(AudioClient.DefaultFormat);
			AudioClient.Source = AudioSequencer = new SimpleSequencerSource();
			AudioSession = AudioSequencer.NewSession();
			AudioSequencer.Playing = true;
			AudioClient.Start();

			ChartPlayer.motionRegistry = new Dictionary<Identifier, MotionRegistry> {
				{ "pt"     , new MotionRegistry(typeof(VecPt)) },
				{ "dir"    , new MotionRegistry(typeof(Vec3)) },
				{ "normal" , new MotionRegistry(typeof(Vec3)) },
				{ "sv"     , new MotionRegistry(new VecPtComp(0f, 0f), new VecPtComp(0f, 1f)) },
				{ "svm"    , new MotionRegistry(new Vec1m(1f)) },
				{ "dist"   , new MotionRegistry(new VecPtComp(0f, 0f), new VecPtComp(0f, float.PositiveInfinity)) },
				{ "corner" , new MotionRegistry(typeof(VecI1)) },
				{ "ctrl0"  , new MotionRegistry(typeof(VecCtrl)) },
				{ "ctrl1"  , new MotionRegistry(typeof(VecCtrl)) },
				{ "track"  , new MotionRegistry(typeof(Vec1)) },
			};

			var dir = new DirectoryInfo(Settings.Default.GameDataPath + "/charts");
			if (!dir.Exists || Settings.Default.LastRunVersion != Application.version) {
				Directory.CreateDirectory(dir.FullName);
				var defaultData = Resources.Load<TextAsset>("default");
				using (var zip = ZipFile.Read(defaultData.bytes)) {
					zip.ExtractExistingFile = ExtractExistingFileAction.OverwriteSilently;
					zip.ExtractAll(Settings.Default.GameDataPath);
				}
			}

			Settings.Default.LastRunVersion = Application.version;
			Settings.Default.Save();

			Logger.Log("main", 1, "Game", "Initialized");
		}

#if UNITY_ANDROID
		[MonoPInvokeCallback(typeof(OpenSL.Native.slBufferQueueCallback))]
		static void audioCallback(IntPtr caller, IntPtr context) {
			Cryville.Audio.OpenSL.OutputClient.Callback(caller, context);
		}
#endif

		static bool _shutdown;
		public static void Shutdown() {
			if (_shutdown) return;
			_shutdown = true;

			Logger.Log("main", 1, "Game", "Shutting down");
			try {
				AudioSequencer.Dispose();
				AudioClient.Dispose();
				AudioManager.Dispose();
			}
			catch (Exception ex) {
				LogException("Game", "An error occured while shutting down", ex);
			}
			finally {
				Logger.Close();
			}
		}

		public static DirectoryInfo[] GetDatabaseList() {
			return new DirectoryInfo(GameDataPath + "/db").GetDirectories();
		}
		public static void ConnectDatabase(string name) {
		}
		public static void DisconnectDatabase() {
		}

		public static void LogException(string module, string prefix, Exception ex) {
			Logger.Log("main", 4, module, "{0}: {1}", prefix, ex);
		}

		static void OnLog(string condition, string stackTrace, LogType type) {
			int l;
			switch (type) {
				case LogType.Log: l = 1; break;
				case LogType.Assert: l = 2; break;
				case LogType.Warning: l = 3; break;
				case LogType.Error:
				case LogType.Exception: l = 4; break;
				default: l = 1; break;
			}
			Logger.Log("main", l, "Internal", "{0}\n{1}", condition, stackTrace);
		}
	}
}
