/*
 * SoundScene.java
 *
 * This is the primary class file for running the game. This scene is different from
 * previous examples in that it uses the LibGDX UI library.
 *
 * @author: Walker M. White
 * @date: 11/21/2024
 */
package edu.cornell.cis3152.sound;

import com.badlogic.gdx.*;
import com.badlogic.gdx.audio.Music;
import com.badlogic.gdx.utils.*;
import com.badlogic.gdx.assets.*;
import com.badlogic.gdx.graphics.*;
import com.badlogic.gdx.scenes.scene2d.*;
import com.badlogic.gdx.scenes.scene2d.utils.*;
import com.badlogic.gdx.scenes.scene2d.ui.*;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.TextureRegion;

import com.badlogic.gdx.utils.viewport.ScalingViewport;
import com.badlogic.gdx.utils.viewport.Viewport;
import edu.cornell.gdiac.assets.*;
import edu.cornell.gdiac.audio.*;
import edu.cornell.gdiac.graphics.*;
import edu.cornell.gdiac.util.ScreenListener;

/**
 * The primary controller class for the game.
 *
 * While GDXRoot is the root class, it delegates all of the work to the player scene
 * classes. This is the player scene class for running the game. In initializes all
 * of the other classes in the game and hooks them together.  It also provides the
 * basic game loop (update-draw).
 */
public class SoundScene implements Screen {
    /** Asset directory with UI features */
    private AssetDirectory directory;
    /** Reference to drawing context to display graphics (VIEW CLASS) */
    private SpriteBatch batch;
    /** The viewport for the stage */
    private Viewport viewport;
    /** Listener for screen input events */
    ScreenListener listener;

    /** Whether or not this player mode is still active */
    private boolean active;

    /** Stage for the UI */
    Stage stage;
    /** The button to click on */
    TextButton buttons[];

    /** List of sounds to play */
    SoundEffect sounds[];
    /** The sounds instances ids */
    long soundIds[];
    /** The current sound effect play */
    int currentSound = 0;

    /** List of music to play */
    AudioSource samples[];
    /** The current music sample to play */
    int currentSample = 0;

    /** A queue to play music */
    MusicQueue music;

    /** An effect filter to apply */
    EffectFilter filter;

    /**
     * Creates a new game with the given drawing context.
     *
     * This constructor initializes the models and controllers for the game.  The
     * view has already been initialized by the root class.
     */
    public SoundScene(AssetDirectory directory, SpriteBatch batch) {
        this.directory = directory;
        this.batch = batch;
        active = false;

        // We will let a LibGDX stage handle all input handling
        viewport = new ScalingViewport(Scaling.stretch, Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), new OrthographicCamera());
        stage = new Stage(viewport,batch);

        Gdx.input.setInputProcessor(stage);
        TextButton.TextButtonStyle textButtonStyle = new TextButton.TextButtonStyle();
        textButtonStyle.font = directory.getEntry( "roboto", BitmapFont.class );
        textButtonStyle.fontColor = Color.WHITE;
        textButtonStyle.downFontColor = Color.LIGHT_GRAY;
        textButtonStyle.checkedFontColor = Color.LIGHT_GRAY;
        textButtonStyle.up   = new TextureRegionDrawable(new TextureRegion(directory.getEntry( "button-up", Texture.class )));
        textButtonStyle.down = new TextureRegionDrawable(new TextureRegion(directory.getEntry( "button-dn", Texture.class )));
        textButtonStyle.checked = new TextureRegionDrawable(new TextureRegion(directory.getEntry( "button-dn", Texture.class )));

        int x = Gdx.graphics.getWidth()/2;
        int y = Gdx.graphics.getHeight()/2;

        buttons = new TextButton[4];
        buttons[0] = new TextButton("SoundFX", textButtonStyle);
        buttons[0].setSize(192, 192);
        buttons[0].setPosition( x - 384, y, Align.center );
        stage.addActor(buttons[0]);

        buttons[1] = new TextButton("Reverb", textButtonStyle);
        buttons[1].setSize(192, 192);
        buttons[1].setPosition( x - 128, y, Align.center );
        stage.addActor(buttons[1]);

        buttons[2] = new TextButton("Music", textButtonStyle);
        buttons[2].setSize(192, 192);
        buttons[2].setPosition( x + 128, y, Align.center );
        stage.addActor(buttons[2]);

        buttons[3] = new TextButton("Skip", textButtonStyle);
        buttons[3].setSize(192, 192);
        buttons[3].setPosition( x + 384, y, Align.center );
        stage.addActor(buttons[3]);

        // Get the sounds
        sounds = new SoundEffect[2];
        sounds[0] = directory.getEntry( "pew", SoundEffect.class );
        sounds[1] = directory.getEntry( "pop", SoundEffect.class );
        soundIds = new long[2];
        soundIds[0] = -1;
        soundIds[1] = -1;
        currentSound = 1;

        samples = new AudioSource[2];
        samples[0] = directory.getEntry( "dodge", AudioSource.class );
        samples[1] = directory.getEntry( "win", AudioSource.class );
        currentSample = 0;

        AudioEngine engine = (AudioEngine)Gdx.audio;
        music = engine.newMusicQueue( false, 44100 );
        music.addSource( samples[0] );
        music.addSource( samples[1] );

        EffectFactory factory = engine.getEffectFactory();
        EffectFactory.ReverbDef def = new EffectFactory.ReverbDef();
        def.REVERB_REFLECTIONS_DELAY = 0.2f;
        filter = factory.createReverb(def);

        // Hook up the buttons
        buttons[0].addListener( new ClickListener() {
            @Override
            public void clicked(InputEvent event, float x, float y) {
                if (buttons[0].isChecked()) {
                    currentSound = currentSound == 0 ? 1 : 0;
                    soundIds[currentSound] = sounds[currentSound].play();
                    if (buttons[1].isChecked()) {
                        sounds[currentSound].addEffect( soundIds[currentSound], filter );
                    }
                } else {
                    sounds[currentSound].stop(soundIds[currentSound]);
                }
            };
        } );

        buttons[1].addListener( new ClickListener() {
            @Override
            public void clicked(InputEvent event, float x, float y) {
                if (buttons[1].isChecked()) {
                    music.addEffect( filter );
                } else {
                    music.clearAllEffect();
                    for(int ii = 0; ii < 2; ii++) {
                        if (sounds[ii].isPlaying( soundIds[ii] )) {
                            sounds[ii].removeEffect( soundIds[ii], filter );
                        }
                    }
                }
            };
        } );

        buttons[2].addListener( new ClickListener() {
            @Override
            public void clicked(InputEvent event, float x, float y) {
                if (buttons[2].isChecked()) {
                    music.play();
                } else {
                    music.pause();
                }
            };
        } );

        buttons[3].addListener( new ClickListener() {
            @Override
            public void clicked(InputEvent event, float x, float y) {
                if (music.getCurrent() == samples[0] ) {
                    music.advanceSource();
                } else {
                    music.reset();
                }
                buttons[3].toggle();
            };
        } );


        // Add listeners to the sounds
        for(int ii = 0; ii < 2; ii++) {
            sounds[ii].setOnCompletionListener( new SoundEffect.OnCompletionListener() {
                @Override
                public void onCompletion(SoundEffect buffer, long instance) {
                    // Pop the button when we complete
                    buttons[0].toggle();
                }
            } );
        }

        music.setOnTransitionListener( new MusicQueue.OnTransitionListener() {
            @Override
            public void onLoopback(MusicQueue buffer, AudioSource source) {
                // Do nothing
            }

            @Override
            public void onTransition(MusicQueue buffer, AudioSource source1, AudioSource source2) {
                if (source2 == samples[0]) {
                    currentSample = 1;
                } else if (source2 == samples[1]) {
                    currentSample = 0;
                }
            }

            @Override
            public void onCompletion(MusicQueue buffer, AudioSource source) {
                currentSample = 0;
                buttons[2].toggle();
            }
        } );
    }

    /**
     * Dispose of all (non-static) resources allocated to this mode.
     */
    public void dispose() {
        stage.dispose();
        stage = null;
    }

    /**
     * Update the game state.
     *
     * We prefer to separate update and draw from one another as separate methods, instead
     * of using the single render() method that LibGDX does.  We will talk about why we
     * prefer this in lecture.
     *
     * @param delta Number of seconds since last animation frame
     */
    private void update(float delta) {
    }

    /**
     * Draw the status of this player mode.
     *
     * We prefer to separate update and draw from one another as separate methods, instead
     * of using the single render() method that LibGDX does.  We will talk about why we
     * prefer this in lecture.
     */
    private void draw(float delta) {
        // Clear the screen (color is homage to the XNA years)
        ScreenUtils.clear(0.39f, 0.58f, 0.93f, 1.0f);

        // But then the stage does everything else
        stage.draw();
    }

    /**
     * Called when the Screen is resized.
     *
     * This can happen at any point during a non-paused state but will never happen
     * before a call to show().
     *
     * @param width  The new width in pixels
     * @param height The new height in pixels
     */
    public void resize(int width, int height) {
        // IGNORE FOR NOW
    }

    /**
     * Called when the Screen should render itself.
     *
     * We defer to the other methods update() and draw().  However, it is VERY important
     * that we only quit AFTER a draw.
     *
     * @param delta Number of seconds since last animation frame
     */
    public void render(float delta) {
        if (active) {
            update(delta);
            draw(delta);
        }
    }

    /**
     * Called when the Screen is paused.
     *
     * This is usually when it's not active or visible on screen. An Application is
     * also paused before it is destroyed.
     */
    public void pause() {
        // TODO Auto-generated method stub
    }

    /**
     * Called when the Screen is resumed from a paused state.
     *
     * This is usually when it regains focus.
     */
    public void resume() {
        // TODO Auto-generated method stub
    }

    /**
     * Called when this screen becomes the current screen for a Game.
     */
    public void show() {
        // Useless if called in outside animation loop
        active = true;
    }

    /**
     * Called when this screen is no longer the current screen for a Game.
     */
    public void hide() {
        // Useless if called in outside animation loop
        active = false;
    }

}
