using DoomDeathmatch.Component.MVC.Controller;
using DoomDeathmatch.Component.MVC.Model.Enemy;
using DoomDeathmatch.Component.MVC.View;
using DoomDeathmatch.Component.UI;
using DoomDeathmatch.Component.Util;
using DoomDeathmatch.Component.Util.Collision;
using Engine.Asset.Mesh;
using Engine.Graphics.Pipeline;
using Engine.Graphics.Texture;
using Engine.Scene;
using Engine.Scene.Component.BuiltIn.Renderer;
using OpenTK.Mathematics;

namespace DoomDeathmatch.Scene.Play;

public static class PlayScene
{
  public static Engine.Scene.Scene Create(Engine.Engine parEngine)
  {
    var scene = new Engine.Scene.Scene();

    var (hudCameraObject, hudCamera) = UiUtil.CreateOrthographicCamera(scene, RenderLayer.HUD);
    var (uiContainerObject, uiContainer) =
      UiUtil.CreateContainerUi(scene, new UiContainerComponent { Camera = hudCamera });

    var menuController = new MenuControllerComponent();
    var menuControllerObject = GameObjectUtil.CreateGameObject(scene, [
      menuController
    ]);

    var (playUiObject, (playWeaponView, playHealthView, playScoreView, plaTimerView)) =
      CreateGameUi(parEngine, scene, uiContainer, menuController);
    menuController.AddMenuItem("play", playUiObject);
    scene.AddChild(uiContainerObject, playUiObject);

    var escapeUiObject = CreateEscapeMenu(parEngine, scene, uiContainer, menuController);
    escapeUiObject.IsEnabled = false;
    menuController.AddMenuItem("escape", escapeUiObject);
    scene.AddChild(uiContainerObject, escapeUiObject);

    var (perspectiveCameraObject, perspectiveCamera) = UiUtil.CreatePerspectiveCamera(scene);
    perspectiveCameraObject.Transform.Translation.Z = 2;
    var playerObject = GameObjectUtil.CreateGameObject(scene, [
      new ControllerComponent(),

      new MovementController { Speed = 10f },
      new RigidbodyComponent(),
      new DragComponent { Drag = 10f, Coefficient = new Vector3(1, 1, 0) },

      new AABBColliderComponent
      {
        Collider = new AABBCollider { Size = new Vector3(1, 1, 2) },
        Offset = new Vector3(0, 0f, 1f),
        ColliderGroups = { "player" },
        ExcludeColliderCollideGroups = { "player" }
      },

      new PlayerController(perspectiveCamera),

      new WeaponController(),
      playWeaponView,

      new HealthController(),
      playHealthView,
    ]);
    playerObject.Transform.Translation.X = -3;
    var gameControllerObject = GameObjectUtil.CreateGameObject(scene, [
      new GameController(menuController),

      new TimerController(),
      plaTimerView,

      new ScoreController(),
      playScoreView,

      new CollisionManager(),
    ]);

    scene.AddChild(playerObject, perspectiveCameraObject);

    var mapObject = LoadMap(parEngine, scene, "default");
    var impObject = EnemyObject.CreateEnemy(parEngine, scene, EnemyData.Imp);

    return scene;
  }

  private static GameObject LoadMap(Engine.Engine parEngine, Engine.Scene.Scene parScene, string parMapName)
  {
    var mapObject = GameObjectUtil.CreateGameObject(parScene, [
      new MeshRenderer
      {
        Mesh = parEngine.AssetResourceManager.Load<Mesh>($"map/{parMapName}/mesh.obj"),
        Albedo = parEngine.AssetResourceManager.Load<Texture>($"map/{parMapName}/texture.png")
      }
    ]);

    var colliders = parEngine.AssetResourceManager.Load<Mesh>($"map/{parMapName}/colliders.obj");
    var collidersObject = MeshToColliders(parScene, colliders);

    var monsterSpawnPoints = MeshToSpawnPoints(
      parEngine.AssetResourceManager.Load<Mesh>($"map/{parMapName}/monster_spawners.obj"));

    var valuableSpawnPoints = MeshToSpawnPoints(
      parEngine.AssetResourceManager.Load<Mesh>($"map/{parMapName}/valuable_spawners.obj"));

    parScene.AddChild(mapObject, collidersObject);

    return mapObject;
  }

  private static List<Vector3> MeshToSpawnPoints(Mesh parMesh)
  {
    return parMesh.Indices.Select(parIndex => parMesh.Vertices[(int)parIndex]._position).ToList();
  }

  private static GameObject MeshToColliders(Engine.Scene.Scene parScene, Mesh parMesh)
  {
    var allCollidersObject = GameObjectUtil.CreateGameObject(parScene);

    if (parMesh.Indices.Count % (4 * 6) != 0)
    {
      throw new InvalidOperationException("Mesh is not an AABB collider");
    }

    for (var i = 0; i < parMesh.Indices.Count; i += 24)
    {
      var max = new Vector3(float.MinValue);
      var min = new Vector3(float.MaxValue);
      for (var j = 0; j < 24; j++)
      {
        var position = parMesh.Vertices[(int)parMesh.Indices[i + j]]._position;
        max = Vector3.ComponentMax(max, position);
        min = Vector3.ComponentMin(min, position);
      }

      var size = max - min;
      var offset = min + size / 2;

      var colliderObject = GameObjectUtil.CreateColliderForceField(
        parScene,
        new AABBColliderComponent
        {
          Collider = new AABBCollider { Size = size }, Offset = offset, ColliderGroups = { "wall" }
        }
      );

      parScene.AddChild(allCollidersObject, colliderObject);
    }

    return allCollidersObject;
  }

  private static (GameObject, (WeaponView, HealthView, ScoreView, TimerView)) CreateGameUi(Engine.Engine parEngine,
    Engine.Scene.Scene parScene,
    UiContainerComponent parUiContainer, MenuControllerComponent parMenuController)
  {
    var parentObject = GameObjectUtil.CreateGameObject(parScene);

    var (bottomContainerObject, bottomContainer) =
      UiUtil.CreateContainerUi(parScene, new UiContainerComponent { Container = parUiContainer });
    bottomContainer.Anchor = Anchor.BottomCenter;
    bottomContainer.Center = Anchor.BottomCenter;
    bottomContainerObject.AddComponent(new Box2DRenderer
    {
      Color = new Vector4(1, 0, 0, 1), RenderLayer = RenderLayer.HUD
    });
    bottomContainerObject.AddComponent(new CopySizeComponent
    {
      Target = parUiContainer.GameObject.Transform, Coefficient = new Vector3(1, 0, 1)
    });
    bottomContainerObject.Transform.Size.Y = 1.5f;
    parScene.AddChild(parentObject, bottomContainerObject);

    var (gunObject, (gunUi, gunSprite)) = UiUtil.CreateSpriteUi(parScene, bottomContainer,
      null, RenderLayer.HUD);
    gunObject.Transform.Scale = new Vector3(5);
    gunUi.Anchor = Anchor.TopCenter;
    gunUi.Center = Anchor.BottomCenter;
    gunUi.Offset = new Vector2(0, -0.05f);
    parScene.AddChild(bottomContainerObject, gunObject);

    var (healthObject, healthUi, (_, healthTextRenderer)) =
      UiUtil.CreateTextUi(parScene, bottomContainer, UiUtil.GetDoomFont(parEngine), "Здоровье: 000",
        TextAlignComponent.Align.Center,
        RenderLayer.HUD);
    healthObject.Transform.Scale = new Vector3(0.75f);
    healthUi.Anchor = Anchor.CenterLeft;
    healthUi.Center = Anchor.CenterLeft;
    parScene.AddChild(bottomContainerObject, healthObject);

    var (ammoObject, ammoUi, (_, ammoTextRenderer)) =
      UiUtil.CreateTextUi(parScene, bottomContainer, UiUtil.GetDoomFont(parEngine), "Патроны: 00/00",
        TextAlignComponent.Align.Center,
        RenderLayer.HUD);
    ammoObject.Transform.Scale = new Vector3(0.75f);
    ammoUi.Anchor = Anchor.TopRight;
    ammoUi.Center = Anchor.TopRight;
    parScene.AddChild(bottomContainerObject, ammoObject);

    var (weaponObject, weaponUi, (_, weaponTextRenderer)) =
      UiUtil.CreateTextUi(parScene, bottomContainer, UiUtil.GetDoomFont(parEngine), "Оружие: ОРУЖИЕОР",
        TextAlignComponent.Align.Center,
        RenderLayer.HUD);
    weaponObject.Transform.Scale = new Vector3(0.75f);
    weaponUi.Anchor = Anchor.BottomRight;
    weaponUi.Center = Anchor.BottomRight;
    parScene.AddChild(bottomContainerObject, weaponObject);

    var (topContainerObject, topContainer) =
      UiUtil.CreateContainerUi(parScene, new UiContainerComponent { Container = parUiContainer });
    topContainer.Anchor = Anchor.TopCenter;
    topContainer.Center = Anchor.TopCenter;
    topContainerObject.AddComponent(
      new Box2DRenderer { Color = new Vector4(1, 0, 0, 1), RenderLayer = RenderLayer.HUD });
    topContainerObject.AddComponent(new CopySizeComponent
    {
      Target = parUiContainer.GameObject.Transform, Coefficient = new Vector3(1, 0, 1)
    });
    topContainerObject.Transform.Size.Y = 1f;
    parScene.AddChild(parentObject, topContainerObject);

    var (timerObject, timerUi, (_, timerTextRenderer)) =
      UiUtil.CreateTextUi(parScene, topContainer, UiUtil.GetDoomFont(parEngine), "Время: 00:00",
        parRenderLayer: RenderLayer.HUD);
    timerUi.Anchor = Anchor.CenterLeft;
    timerUi.Center = Anchor.CenterLeft;
    parScene.AddChild(topContainerObject, timerObject);

    var (scoreObject, scoreUi, (_, scoreTextRenderer)) =
      UiUtil.CreateTextUi(parScene, topContainer, UiUtil.GetDoomFont(parEngine), "Счет: 00000",
        parRenderLayer: RenderLayer.HUD);
    scoreUi.Anchor = Anchor.CenterRight;
    scoreUi.Center = Anchor.CenterRight;
    parScene.AddChild(topContainerObject, scoreObject);

    var weaponView = new WeaponView(weaponTextRenderer, ammoTextRenderer, gunSprite);
    var healthView = new HealthView(healthTextRenderer);
    var scoreView = new ScoreView(scoreTextRenderer);
    var timerView = new TimerView(timerTextRenderer);

    return (parentObject, (weaponView, healthView, scoreView, timerView));
  }

  private static GameObject CreateEscapeMenu(Engine.Engine parEngine, Engine.Scene.Scene parScene,
    UiContainerComponent parUiContainer, MenuControllerComponent parMenuController)
  {
    var parentObject = GameObjectUtil.CreateGameObject(parScene);

    var (backgroundObject, backgroundUiContainer) = UiUtil.CreateBackgroundUi(parScene,
      new UiContainerComponent { Container = parUiContainer }, new Vector4(0, 0, 0, 0.9f), RenderLayer.HUD);
    backgroundObject.AddComponent(new CopySizeComponent { Target = parUiContainer.GameObject.Transform });
    parScene.AddChild(parentObject, backgroundObject);

    var (logoObject, logoUi) = UiUtil.CreateLogoUi(parEngine, parScene, backgroundUiContainer, RenderLayer.HUD);
    logoUi.Offset = new Vector2(0, 3f);
    parScene.AddChild(parentObject, logoObject);

    var (backUiObject, backUi, _) = UiUtil.CreateTextUi(parScene, backgroundUiContainer,
      UiUtil.GetDoomFont(parEngine), "Назад",
      parRenderLayer: RenderLayer.HUD);
    backUi.OnClick += parUiComponent =>
      parUiComponent.GameObject.Scene!.FindFirstComponent<GameController>()!.Unpause();

    var (exitUiObject, exitUi, _) = UiUtil.CreateTextUi(parScene, backgroundUiContainer,
      UiUtil.GetDoomFont(parEngine), "Выход",
      parRenderLayer: RenderLayer.HUD);
    exitUi.OnClick += _ => parEngine.SceneManager.TransitionTo(() => MainScene.Create(parEngine));

    var (stackObject, stack) = UiUtil.CreateStackUi(parScene,
      new StackComponent
      {
        Offset = new Vector2(0, -1f), Container = backgroundUiContainer, Children = { backUi, exitUi }
      });
    stackObject.Transform.Size.Xy = new Vector2(1f, 6f);

    var (selectorObject, selector) = UiUtil.CreateSelectorUi(parScene,
      new SelectorComponent { Children = { backUi, exitUi } }, RenderLayer.HUD);

    parScene.AddChild(parentObject, selectorObject);
    parScene.AddChild(parentObject, stackObject);
    parScene.AddChild(stackObject, backUiObject);
    parScene.AddChild(stackObject, exitUiObject);

    return parentObject;
  }
}