/*
 * +================================================================================+
 *      __  ___  ______________  __  __  ______________   ____
 *     / / / / |/ /  _/_  __/\ \/ / / / / /_  __/  _/ /  / __/
 *    / /_/ /    // /  / /    \  / / /_/ / / / _/ // /___\ \
 *    \____/_/|_/___/ /_/     /_/  \____/ /_/ /___/____/___/
 *
 * +================================================================================+
 *
 *    "THE BEER-WARE LICENSE"
 *    -----------------------
 *
 *    As long as you retain this notice you can do whatever you want with this
 *    stuff. However it is expressly forbidden to sell or commercially distribute
 *    Unity Utils or parts of it apart from your games.
 *    If you meet any of the authors some day, and you think this stuff is
 *    worth it, you can buy them a beer in return.
 *
 *    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *    THE SOFTWARE.
 *
 * +================================================================================+
 *
 *    Authors:
 *    David Krummenacher (dave@timbookedtwo.com)
 *
 */

using System.Collections;
using UnityEngine;

namespace Utils.Helpers {

	/// <summary>
	/// Helper for level loading.
	/// </summary>
	public abstract class LevelHelper {

		/// <summary>
		/// Reloads the current level.
		/// </summary>
		public static void ReloadCurrentLevel() {
			Application.LoadLevel(Application.loadedLevel);
		}
	
		/// <summary>
		/// Loads the next level.
		/// </summary>
		/// <param name="index">Index of the level. -1 for current level.</param>
		/// <param name="cycle">Determines what happens if it's the last level. If set to <c>true</c> loads the first level, if set to <c>false</c> loads the last level.</param>
		public static void LoadNextLevel(int index = -1, bool cycle = true) {
			Application.LoadLevel(NextLevel(index, cycle));
		}

		/// <summary>
		/// Loads the previous level.
		/// </summary>
		/// <param name="index">Index of the level. -1 for current level.</param>
		/// <param name="cycle">Determines what happens if it's the first level. If set to <c>true</c> loads the last level, if set to <c>false</c> loads the first level.</param>
		public static void LoadPreviousLevel(int index = -1, bool cycle = true) {
			Application.LoadLevel(PreviousLevel(index, cycle));
		}

		/// <summary>
		/// Loads the first level or a level with a given offset to the first level.
		/// </summary>
		/// <param name="offset">Offset in positive number.</param>
		public static void LoadFirstLevel(int offset = 0) {
			if (offset >= Application.levelCount) {
				Debug.LogWarning("Offset to big. Loading last level!");
				offset = Application.levelCount - 1;
			}
			if (offset < 0) {
				Debug.LogWarning("Offset smaller than zero. Loading first level!");
				offset = 0;
			}
			Application.LoadLevel(0 + offset);
		}

		/// <summary>
		/// Loads the last level or a level with a given offset to the last level.
		/// </summary>
		/// <param name="offset">Offset in positive number.</param>
		public static void LoadLastLevel(int offset = 0) {
			if (offset >= Application.levelCount) {
				Debug.LogWarning("Offset to big. Loading first level!");
				offset = Application.levelCount - 1;
			}
			if (offset < 0) {
				Debug.LogWarning("Offset smaller than zero. Loading last level!");
				offset = 0;
			}
			Application.LoadLevel(Application.levelCount - 1 - offset);
		}

		/// <summary>
		/// Gets the index of the next level.
		/// </summary>
		/// <returns>The next levels index.</returns>
		/// <param name="index">Index of the level. -1 for current level.</param>
		/// <param name="cycle">Determines what happens if it's the last level. If set to <c>true</c> returns the first level, if set to <c>false</c> returns the last level.</param>
		public static int NextLevel(int index = -1, bool cycle = true) {
			int next = (index >= 0) ? index : Application.loadedLevel;
			next++;
			if (next >= Application.levelCount) {
				if (cycle) next -= Application.levelCount;
				else next = Application.levelCount - 1;
			}
			return next;
		}

		/// <summary>
		/// Gets the index of the previous level.
		/// </summary>
		/// <returns>The previous levels index.</returns>
		/// <param name="index">Index of the level. -1 for current level.</param>
		/// <param name="cycle">Determines what happens if it's the first level. If set to <c>true</c> returns the last level, if set to <c>false</c> returns the first level.</param>
		public static int PreviousLevel(int index = -1, bool cycle = true) {
			int previous = (index >= 0) ? index : Application.loadedLevel;
			previous--;
			if (previous < 0) {
				if (cycle) previous += Application.levelCount;
				else previous = 0;
			}
			return previous;
		}

		/// <summary>
		/// Checks if it is the first level.
		/// </summary>
		/// <returns><c>true</c> if is the first level; otherwise, <c>false</c>.</returns>
		/// <param name="index">Index of the level. -1 for current level.</param>
		public static bool IsFirstLevel(int index = -1) {
			if (index >= 0) return index == 0;
			else return Application.loadedLevel == 0;
		}
	
		/// <summary>
		/// Checks if it is the last level.
		/// </summary>
		/// <returns><c>true</c> if is the last level; otherwise, <c>false</c>.</returns>
		/// <param name="index">Index of the level. -1 for current level.</param>
		public static bool IsLastLevel(int index = -1) {
			if (index >= 0) return index == Application.levelCount - 1;
			else return Application.loadedLevel == Application.levelCount - 1;
		}

		/// <summary>
		/// Loads the level with loading screen.
		/// </summary>
		/// <returns>The level loader component for progress etc.</returns>
		/// <param name="loadingScreen">The scene with that acts as the loading screen.</param>
		/// <param name="levelToLoad">The level to load.</param>
		public static LevelLoader LoadLevelWithLoadingScreen(string loadingScreen, string levelToLoad) {
			GameObject levelLoaderObject = new GameObject();
			LevelLoader levelLoader = levelLoaderObject.AddComponent<LevelLoader>();
			levelLoader.LoadScene(loadingScreen, levelToLoad);
			return levelLoader;
		}

		/// <summary>
		/// Loads the level with loading screen.
		/// </summary>
		/// <returns>The level loader component for progress etc.</returns>
		/// <param name="loadingScreen">The scene with that acts as the loading screen.</param>
		/// <param name="levelToLoad">The level to load.</param>
		public static LevelLoader LoadLevelWithLoadingScreen(int loadingScreen, int levelToLoad) {
			GameObject levelLoaderObject = new GameObject();
			LevelLoader levelLoader = levelLoaderObject.AddComponent<LevelLoader>();
			levelLoader.LoadScene(loadingScreen, levelToLoad);
			return levelLoader;
		}

		/// <summary>
		/// Level loader component used for loading levels with a loading screen.
		/// </summary>
		public class LevelLoader : MonoBehaviour {

			/// <summary>
			/// The asynchronous operation.
			/// </summary>
			private AsyncOperation asyncOperation = null;

			/// <summary>
			/// Gets the progress of the loading.
			/// </summary>
			/// <value>The progress.</value>
			public float Progress {
				get {
					if (asyncOperation != null) return asyncOperation.progress;
					return 0f;
				}
			}

			/// <summary>
			/// Gets a value indicating whether the loading is done.
			/// </summary>
			/// <value><c>true</c> if the loading is done; otherwise, <c>false</c>.</value>
			public bool IsDone {
				get {
					if (asyncOperation != null) return asyncOperation.isDone;
					return false;
				}
			}

			void Awake() { DontDestroyOnLoad(this); }

			/// <summary>
			/// Loads the scene.
			/// </summary>
			/// <param name="loadingScreen">The scene that acts as the loading screen.</param>
			/// <param name="levelToLoad">The level to load.</param>
			public void LoadScene(string loadingScreen, string levelToLoad) {
				Application.LoadLevel(loadingScreen);
				StartCoroutine(LoadLevelAsync(loadingScreen, levelToLoad));
			}

			/// <summary>
			/// Loads the scene.
			/// </summary>
			/// <param name="loadingScreen">The scene that acts as the loading screen.</param>
			/// <param name="levelToLoad">The level to load.</param>
			public void LoadScene(int loadingScreen, int levelToLoad) {
				Application.LoadLevel(loadingScreen);
				StartCoroutine(LoadLevelAsync(loadingScreen, levelToLoad));
			}

			/// <summary>
			/// Enters the loaded scene.
			/// </summary>
			public void EnterLoadedScene() {
				if (asyncOperation != null) asyncOperation.allowSceneActivation = true;
			}

			/// <summary>
			/// Coroutine that loads the level asynchronously.
			/// </summary>
			/// <returns>The asynchronous operation.</returns>
			/// <param name="loadingScreen">The scene that acts as the loading screen.</param>
			/// <param name="levelToLoad">The level to load.</param>
			private IEnumerator LoadLevelAsync(string loadingScreen, string levelToLoad) {
				asyncOperation = Application.LoadLevelAsync(levelToLoad);
				asyncOperation.allowSceneActivation = false;
				yield return asyncOperation;
			}

			/// <summary>
			/// Coroutine that loads the level asynchronously.
			/// </summary>
			/// <returns>The asynchronous operation.</returns>
			/// <param name="loadingScreen">The scene that acts as the loading screen.</param>
			/// <param name="levelToLoad">The level to load.</param>
			private IEnumerator LoadLevelAsync(int loadingScreen, int levelToLoad) {
				asyncOperation = Application.LoadLevelAsync(levelToLoad);
				asyncOperation.allowSceneActivation = false;
				yield return asyncOperation;
			}
		}
	}
}
