package funkin.play;

import flixel.FlxG;
import flixel.FlxObject;
import flixel.FlxSprite;
import flixel.sound.FlxSound;
import funkin.ui.story.StoryMenuState;
import flixel.util.FlxColor;
import flixel.util.FlxTimer;
import funkin.modding.events.ScriptEvent;
import funkin.modding.events.ScriptEventDispatcher;
import funkin.play.PlayState;
import funkin.play.character.BaseCharacter;
import funkin.ui.PreferencesMenu;

/**
 * A substate which renders over the PlayState when the player dies.
 * Displays the player death animation, plays the music, and handles restarting the song.
 *
 * The newest implementation uses a substate, which prevents having to reload the song and stage each reset.
 */
class GameOverSubState extends MusicBeatSubState
{
  /**
   * Which alternate animation on the character to use.
   * You can set this via script.
   * For example, playing a different animation when BF dies in Week 4
   * or Pico dies in Weekend 1.
   */
  public static var animationSuffix:String = "";

  /**
   * Which alternate game over music to use.
   * You can set this via script.
   * For example, the bf-pixel script sets this to `-pixel`
   * and the pico-playable script sets this to `Pico`.
   */
  public static var musicSuffix:String = "";

  /**
   * Which alternate "blue ball" sound effect to use.
   */
  public static var blueBallSuffix:String = "";

  /**
   * The boyfriend character.
   */
  var boyfriend:BaseCharacter;

  /**
   * The invisible object in the scene which the camera focuses on.
   */
  var cameraFollowPoint:FlxObject;

  /**
   * The music playing in the background of the state.
   */
  var gameOverMusic:FlxSound = new FlxSound();

  /**
   * Whether the player has confirmed and prepared to restart the level.
   * This means the animation and transition have already started.
   */
  var isEnding:Bool = false;

  public function new()
  {
    super();
  }

  /**
   * Reset the game over configuration to the default.
   */
  public static function reset()
  {
    animationSuffix = "";
    musicSuffix = "";
  }

  override public function create()
  {
    super.create();

    //
    // Set up the visuals
    //

    // Add a black background to the screen.
    // We make this transparent so that we can see the stage underneath during debugging.
    var bg = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.BLACK);
    bg.alpha = 0.25;
    bg.scrollFactor.set();
    add(bg);

    // Pluck Boyfriend from the PlayState and place him (in the same position) in the GameOverSubState.
    // We can then play the character's `firstDeath` animation.
    boyfriend = PlayState.instance.currentStage.getBoyfriend(true);
    boyfriend.isDead = true;
    add(boyfriend);
    boyfriend.resetCharacter();

    // Assign a camera follow point to the boyfriend's position.
    cameraFollowPoint = new FlxObject(PlayState.instance.cameraFollowPoint.x, PlayState.instance.cameraFollowPoint.y, 1, 1);
    cameraFollowPoint.x = boyfriend.getGraphicMidpoint().x;
    cameraFollowPoint.y = boyfriend.getGraphicMidpoint().y;
    add(cameraFollowPoint);

    FlxG.camera.target = null;
    FlxG.camera.follow(cameraFollowPoint, LOCKON, 0.01);

    //
    // Set up the audio
    //

    // Prepare the game over music.
    FlxG.sound.list.add(gameOverMusic);
    gameOverMusic.stop();

    // The conductor now represents the BPM of the game over music.
    Conductor.update(0);
  }

  var hasStartedAnimation:Bool = false;

  override function update(elapsed:Float)
  {
    if (!hasStartedAnimation)
    {
      hasStartedAnimation = true;

      if (boyfriend.hasAnimation('fakeoutDeath') && FlxG.random.bool((1 / 4096) * 100))
      {
        boyfriend.playAnimation('fakeoutDeath', true, false);
      }
      else
      {
        boyfriend.playAnimation('firstDeath', true, false); // ignoreOther is set to FALSE since you WANT to be able to mash and confirm game over!
        // Play the "blue balled" sound. May play a variant if one has been assigned.
        playBlueBalledSFX();
      }
    }

    //
    // Handle user inputs.
    //

    // MOBILE ONLY: Restart the level when tapping Boyfriend.
    if (FlxG.onMobile)
    {
      var touch = FlxG.touches.getFirst();
      if (touch != null)
      {
        if (touch.overlaps(boyfriend))
        {
          confirmDeath();
        }
      }
    }

    // KEYBOARD ONLY: Restart the level when pressing the assigned key.
    if (controls.ACCEPT && blueballed)
    {
      blueballed = false;
      confirmDeath();
    }

    // KEYBOARD ONLY: Return to the menu when pressing the assigned key.
    if (controls.BACK)
    {
      blueballed = false;
      PlayState.instance.deathCounter = 0;
      // PlayState.seenCutscene = false; // old thing...
      gameOverMusic.stop();

      if (PlayStatePlaylist.isStoryMode) FlxG.switchState(new StoryMenuState());
      else
        FlxG.switchState(new FreeplayState());
    }

    if (gameOverMusic.playing)
    {
      // Match the conductor to the music.
      // This enables the stepHit and beatHit events.
      Conductor.update(gameOverMusic.time);
    }
    else
    {
      // Music hasn't started yet.
      switch (PlayStatePlaylist.campaignId)
      {
        // TODO: Make the behavior for playing Jeff's voicelines generic or un-hardcoded.
        // This will simplify the class and make it easier for mods to add death quotes.
        case "week7":
          if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.isAnimationFinished() && !playingJeffQuote)
          {
            playingJeffQuote = true;
            playJeffQuote();
            // Start music at lower volume
            startDeathMusic(0.2, false);
          }
        default:
          // Start music at normal volume once the initial death animation finishes.
          if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.isAnimationFinished())
          {
            startDeathMusic(1.0, false);
            boyfriend.playAnimation('deathLoop' + animationSuffix);
          }
      }
    }

    // Start death music before firstDeath gets replaced
    super.update(elapsed);
  }

  /**
   * Do behavior which occurs when you confirm and move to restart the level.
   */
  function confirmDeath():Void
  {
    if (!isEnding)
    {
      isEnding = true;
      startDeathMusic(1.0, true); // isEnding changes this function's behavior.

      boyfriend.playAnimation('deathConfirm' + animationSuffix, true);

      // After the animation finishes...
      new FlxTimer().start(0.7, function(tmr:FlxTimer) {
        // ...fade out the graphics. Then after that happens...
        FlxG.camera.fade(FlxColor.BLACK, 2, false, function() {
          // ...close the GameOverSubState.
          FlxG.camera.fade(FlxColor.BLACK, 1, true, null, true);
          PlayState.instance.needsReset = true;

          // Readd Boyfriend to the stage.
          boyfriend.isDead = false;
          remove(boyfriend);
          PlayState.instance.currentStage.addCharacter(boyfriend, BF);

          // Close the substate.
          close();
        });
      });
    }
  }

  public override function dispatchEvent(event:ScriptEvent)
  {
    super.dispatchEvent(event);

    ScriptEventDispatcher.callEvent(boyfriend, event);
  }

  /**
   * Starts the death music at the appropriate volume.
   * @param startingVolume
   */
  function startDeathMusic(?startingVolume:Float = 1, force:Bool = false):Void
  {
    var musicPath = Paths.music('gameOver' + musicSuffix);
    if (isEnding)
    {
      musicPath = Paths.music('gameOverEnd' + musicSuffix);
    }
    if (!gameOverMusic.playing || force)
    {
      gameOverMusic.loadEmbedded(musicPath);
      gameOverMusic.volume = startingVolume;
      gameOverMusic.play();
    }
  }

  static var blueballed:Bool = false;

  /**
   * Play the sound effect that occurs when
   * boyfriend's testicles get utterly annihilated.
   */
  public static function playBlueBalledSFX()
  {
    blueballed = true;
    FlxG.sound.play(Paths.sound('fnf_loss_sfx' + blueBallSuffix));
  }

  var playingJeffQuote:Bool = false;

  /**
   * Week 7-specific hardcoded behavior, to play a custom death quote.
   * TODO: Make this a module somehow.
   */
  function playJeffQuote()
  {
    var randomCensor:Array<Int> = [];

    if (PreferencesMenu.getPref('censor-naughty')) randomCensor = [1, 3, 8, 13, 17, 21];

    FlxG.sound.play(Paths.sound('jeffGameover/jeffGameover-' + FlxG.random.int(1, 25, randomCensor)), 1, false, null, true, function() {
      // Once the quote ends, fade in the game over music.
      if (!isEnding && gameOverMusic != null)
      {
        gameOverMusic.fadeIn(4, 0.2, 1);
      }
    });
  }
}