package zero1hd.rhythmbullet.desktop.screens.main;

import java.util.Observable;
import java.util.Observer;
import java.util.concurrent.LinkedBlockingQueue;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.Preferences;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.InputListener;
import com.badlogic.gdx.scenes.scene2d.ui.Button;
import com.badlogic.gdx.scenes.scene2d.ui.ButtonGroup;
import com.badlogic.gdx.scenes.scene2d.ui.Image;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup;
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable;
import com.badlogic.gdx.utils.Align;
import com.badlogic.gdx.utils.Array;

import zero1hd.rhythmbullet.audio.AudioMetadataController;
import zero1hd.rhythmbullet.audio.metadata.AudioMetadata;
import zero1hd.rhythmbullet.audio.MusicController;
import zero1hd.rhythmbullet.graphics.ui.Page;
import zero1hd.rhythmbullet.graphics.ui.components.ScrollingText;
import zero1hd.rhythmbullet.util.ScreenConfiguration;

public class MusicSelectionPage extends Page implements Observer {
	Preferences musicFileAnnotation;
	
	private MusicController mc;
	private AudioMetadataController mmc;
	private MusicSelectableButtonGroup selectables;
	private VerticalGroup vGroup;
	private TextButton back;
	private ScrollPane scrollPane;
	private ScreenConfiguration screenConfig;
	
	private musicSelectionLoaderThread selectionLoaderThread;
	
	private InformationTable musicInfoTable;
	
	private AssetManager assets;
	private Skin skin;
	
	private boolean down, up;
	
	private boolean frameUsed;
	
	private TextButton beginButton;
	
	private float scrollTimer, scrollDelay = 0.2f, scrollDelMod, songSelectionTimer;
	private float musicSelectDelay;
	
	public MusicSelectionPage(AssetManager assetManager, Skin skin, MusicController musicController, AudioMetadataController musicMetadataController, ScreenConfiguration screenConfiguration, ChangeListener backButtonListener, ChangeListener beginButtonListener) {
		super(1, 0);
		this.assets = assetManager;
		this.mc = musicController;
		this.mmc = musicMetadataController;
		this.skin = skin;
		vGroup = new VerticalGroup();
		vGroup.space(10f);
		selectables = new MusicSelectableButtonGroup();
		selectables.setMinCheckCount(0);
		musicFileAnnotation = Gdx.app.getPreferences("music_file_annotation");
		this.screenConfig = screenConfiguration;
		scrollPane = new ScrollPane(vGroup, skin);
		scrollPane.setSize(0.45f*getWidth(), getHeight());
		scrollPane.setOverscroll(false, false);
		scrollPane.setColor(Color.BLUE);
		addActor(scrollPane);
		back = new TextButton("Back", skin);
		back.setWidth(back.getWidth()+20f);
		back.setPosition(getWidth()-back.getWidth()-15f, getHeight() - back.getHeight() - 15f);
		back.addListener(backButtonListener);
		addActor(back);
		back.toFront();
		
		addListener(new InputListener() {
			@Override
			public boolean keyDown(InputEvent event, int keycode) {
				scrollTimer = 0;
				scrollDelMod = 1f;
				if (keycode == Keys.DOWN) {
					down = true;
				}
				if (keycode == Keys.UP) {
					up = true;
				}
				return super.keyDown(event, keycode);
			}
			
			@Override
			public boolean keyUp(InputEvent event, int keycode) {
				if (keycode == Keys.DOWN) {
					down = false;
				}
				
				if (keycode == Keys.UP) {
					up = false;
				}
				return super.keyUp(event, keycode);
			}
		});
		
		musicInfoTable = new InformationTable(getWidth()-scrollPane.getWidth(), getHeight());
		addActor(musicInfoTable);
		
		beginButton = new TextButton("Begin", skin);
		beginButton.addListener(new ChangeListener() {
			@Override
			public void changed(ChangeEvent event, Actor actor) {
				if (musicSelectDelay != 0) {
					mc.setMusicByFileHandle(getSelectedMusic());
					musicSelectDelay = 0f;
				}
			}
		});
		beginButton.addListener(beginButtonListener);
		mmc.addObserver(this);
		mc.addObserver(this);
		mc.getMusicList().addObserver(this);
		selectionLoaderThread = new musicSelectionLoaderThread();
		
		musicInfoTable.setToDefault();
	}
	
	@Override
	public void act(float delta) {
		
		if (frameUsed) {
			frameUsed = false;
		}
		
		if (down) {
			if (scrollDelMod > 0.25f) {
				scrollDelMod -= delta/0.5f;
			}
			if (scrollTimer <= 0) {
				scrollTimer = scrollDelay*scrollDelMod;
				scrollDown();

			} else {
				scrollTimer -= delta;
			}
		}
		
		if (up) {
			if (scrollDelMod > 0.25f) {
				scrollDelMod -= delta/0.5f;
			}
			if (scrollTimer <= 0) {
				scrollTimer = scrollDelay*scrollDelMod;
				scrollUp();
			} else {
				scrollTimer -= delta;
			}
		}
		
		if (songSelectionTimer > 0f) {
			songSelectionTimer -= delta;
			if (songSelectionTimer <= 0f) {
			}
		}
		if (mc.getMusicList().isSearched()) {
			if (mc.getMusicList().getTotal() != 0) {
				
			}
		}
		
		updateList(delta);
		
		super.act(delta);
	}
	
	private synchronized void updateList(float delta) {
		if (mc.getMusicList().isSearched()) {
			if (mc.getMusicList().getTotal() != 0) {
				if (mmc.isSameSizeMusicList() && !mmc.isSearching()) {
					if (selectables.size() != mmc.size()) {
						MusicSelectable selectable = new MusicSelectable(mmc.getAudioMetadata(selectables.size()));
						selectables.add(selectable);
					} else if (selectables.size() != vGroup.getChildren().size) {
						vGroup.addActor(selectables.getButtons().get(vGroup.getChildren().size));
					} else {
						if (selectables.getChecked() == null) {
							selectables.setMinCheckCount(1);
							selectables.setChecked(mc.getCurrentMusicFileHandle());
							scrollPane.scrollTo(selectables.getChecked().getX(), selectables.getChecked().getY(), selectables.getChecked().getWidth(), selectables.getChecked().getHeight());
						} else if (selectables.getChecked().getMetadata().getFileHandle() != mc.getCurrentMusicFileHandle()) {
							musicSelectDelay += delta;
							if (musicSelectDelay >= 1f) {
								mc.setMusicByFileHandle(selectables.getChecked().getMetadata().getFileHandle());
								musicSelectDelay = 0;
							}
						}
					}
				}

			} else {
				//TODO: Error message reporting empty music list or something
			}
		}
	}
	
	private void scrollDown() {
		if (selectables.selectNext()) {
			scrollPane.scrollTo(selectables.getChecked().getX(), selectables.getChecked().getY(), selectables.getChecked().getWidth(), selectables.getChecked().getHeight());
		}
	}
	
	private void scrollUp() {
		if (selectables.selectPrevious()) {
			scrollPane.scrollTo(selectables.getChecked().getX(), selectables.getChecked().getY(), selectables.getChecked().getWidth(), selectables.getChecked().getHeight());
		}
	}
	
	public FileHandle getSelectedMusic() {
		return selectables.getChecked().getMetadata().getFileHandle();
	}
	
	@Override
	public void dispose() {
		mc.getMusicList().deleteObserver(this);
		selectionLoaderThread.stop();
		super.dispose();
	}
	
	@Override
	public void update(Observable o, Object arg) {
		if (o == mmc) {
			selectionLoaderThread.start();
		} else if (o == mc) {
			if (selectables.getChecked() != null && mc.getMusicList().getTotal() == selectables.size() && mc.getCurrentMusicFileHandle() != selectables.getChecked().getMetadata().getFileHandle()) {
				selectables.setChecked(mc.getCurrentMusicFileHandle());
			}
		} else if (o == mc.getMusicList()) {
			if (arg == mc.getMusicList().states.LOADING) {
				synchronized (this) {
					vGroup.clear();
					selectables.clear();
					musicInfoTable.setToDefault();
					selectionLoaderThread.clear();
				}
			}

		}
	}
	
	@Override
	public void setCameraPositionToPage(Vector3 cameraPosition) {
		getStage().setKeyboardFocus(this);
		getStage().setScrollFocus(scrollPane);
		super.setCameraPositionToPage(cameraPosition);
	}
	
	private class musicSelectionLoaderThread implements Runnable {
		private Thread thread;
 		private String name = "Music-Selection-Loader-Thread";
		private volatile boolean work = true;
		private LinkedBlockingQueue<MusicSelectable> queue;
		public musicSelectionLoaderThread() {
			queue = new LinkedBlockingQueue<>();
		}
		
		@Override
		public void run() {
			while (work) {
				try {
					MusicSelectable selectable = queue.take();
					if (!selectable.isOffScreen()) {
						selectable.getMetadata().loadAlbumCover();
						selectable.loadAttempted();
						simpleDebug("Loaded album cover of " + selectable.getMetadata().getTitle());
					} else {
						simpleDebug("Skipping " + selectable.getMetadata().getTitle());
					}
				} catch (InterruptedException e) {
					simpleDebug("Thread was interupted.");
				}
			}
		}
		
		public boolean start() {
			if (thread == null) {
				thread = new Thread(this, name);
				thread.setDaemon(true);
				thread.start();
				return true;
			}
			return false;
		}
		
		public void stop() {
			clear();
			if (thread != null) {
				thread.interrupt();
			}
			work = false;
		}
		
		public void clear() {
			queue.clear();
		}
		
		public void queue(MusicSelectable selectable) {
			queue.add(selectable);
		}
		
	}
	
	private class MusicSelectable extends Button {
		private Vector2 actualCoords;
		private Image albumCoverImage;
		private Table informationTable;
		private Label name, artist;
		private Label time;
		private float timeSinceChanged;
		private AudioMetadata metadata;
		private Texture defaultAlbumArt;
		private TextureRegion albumArtTexture;
		private volatile boolean offScreen, albumArtDisplayed, albumArtQueued, noReset;
		
		public MusicSelectable(AudioMetadata metadata) {
			super(skin, "selectable-button");
			
			this.metadata = metadata;
			this.defaultAlbumArt = assets.get("defaultCover.png");
			albumArtTexture = new TextureRegion(defaultAlbumArt);
			
			albumCoverImage = new Image();
			updateAlbumArtImage(defaultAlbumArt);
			
			informationTable = new Table();
			informationTable.row().width(0.75f*getWidth());
			name = new Label(metadata.getTitle(), skin, "default-font", skin.getColor("default"));
			name.setEllipsis(true);
			informationTable.add(name).colspan(2).left().expand();
			informationTable.row();
			artist = new Label(metadata.getAuthor(), skin, "sub-font", skin.getColor("default"));
			artist.setEllipsis(true);
			informationTable.add(artist).left().width(getWidth()*0.375f);
			time = new Label(metadata.getDuration(), skin, "sub-font", skin.getColor("default"));
			informationTable.add(time).right();
			add(informationTable).spaceRight(15f).padLeft(15f);

			add(albumCoverImage).right().expandX().size(informationTable.getMinHeight());

			albumCoverImage.setSize(100, 100);
			actualCoords = new Vector2();
		}

		@Override
		public void act(float delta) {
			actualCoords.x = getX() + getParent().getX();
			actualCoords.y = getY() + getParent().getY();

			if ((actualCoords.y < 0 - getHeight() - getStage().getHeight()*0.5f || actualCoords.y > getStage().getHeight()*1.5f) && selectables.getChecked() != this) {
				offScreenAct(delta);
			} else {
				onScreenAct(delta);
			}
			super.act(delta);
		}

		@Override
		public void draw(Batch batch, float parentAlpha) {
			super.draw(batch, parentAlpha);
		}
		
		public void onScreenAct(float delta) {
			if (offScreen) {
				offScreen = false;
				timeSinceChanged = 0;
			} else if (timeSinceChanged < 0.05f) {
				timeSinceChanged += delta;
			} else {
				if (!frameUsed && metadata.getAlbumCover() != null && !albumArtDisplayed) {
					updateAlbumArtImage(metadata.getAlbumCover());
					albumArtDisplayed = true;
					frameUsed = true;
				} else if (!albumArtQueued) {
					selectionLoaderThread.queue(this);
					albumArtQueued = true;
				}
			}
		}

		private void updateAlbumArtImage(Texture texture) {
			if (texture == null) throw new IllegalArgumentException("Texture can't be null!");
			albumArtTexture.setRegion(texture);
			albumCoverImage.setDrawable(new TextureRegionDrawable(albumArtTexture));
		}
		
		public void offScreenAct(float delta) {
			if (!offScreen) {
				offScreen = true;
				timeSinceChanged = 0;
			}
			if (metadata.getAlbumCover() != null) {
				timeSinceChanged += delta;
				if (timeSinceChanged >= 2) {
					updateAlbumArtImage(defaultAlbumArt);
					metadata.unloadAlbumCover();
					albumArtDisplayed = false;
					albumArtQueued = false;
					noReset = false;
				}
			}
		}
		
		public AudioMetadata getMetadata() {
			return metadata;
		}
		
		public FileHandle getFileHandle() {
			return metadata.getFileHandle();
		}
		
		@Override
		public float getPrefWidth() {
			return scrollPane.getScrollWidth();
		}
		
		@Override
		public float getPrefHeight() {
			return super.getPrefHeight();
		}
		
		public TextureRegion getAlbumArtTexture() {
			return albumArtTexture;
		}
		
		public boolean isOffScreen() {
			if (offScreen && !noReset) {
				albumArtDisplayed = false;
				albumArtQueued = false;
			}
			return offScreen;
		}
		
		public void loadAttempted() {
			noReset = true;
		}
	}
	
	private class MusicSelectableButtonGroup extends ButtonGroup<MusicSelectable> {
		private Array<MusicSelectable> buttons;
		
		public MusicSelectableButtonGroup() {
			buttons = getButtons();
		}
		
		public void setChecked(FileHandle fileHandle) {
			if (fileHandle == null) throw new IllegalArgumentException("fileHandle can't be null.");
			MusicSelectable button;
			for (int i = 0; i < buttons.size; i++) {
				button = buttons.get(i);
				if (button.getFileHandle() == fileHandle) {
					button.setChecked(true);
					return;
				}
			}
			
		}
		
		public boolean selectNext() {
			int index = getCheckedIndex() + 1;
			if (index == buttons.size) {
				return false;
			}
			buttons.get(index).setChecked(true);
			return true;
		}
		
		public boolean selectPrevious() {
			int index = getCheckedIndex() - 1;
			if (index == -1) {
				return false;
			}
			buttons.get(index).setChecked(true);
			return true;
		}
		
		@Override
		protected boolean canCheck(MusicSelectable button, boolean newState) {
			if (newState) {
				musicInfoTable.setDisplayedSelectable(button);
				musicSelectDelay = 0f;
			}
			return super.canCheck(button, newState);
		}
		
		public int size() {
			return buttons.size;
		}
	}
	
	private class InformationTable extends Table {
		private ScrollingText songTitle;
		private Label author;
		private Label musicDuration;
		private Label previousTop;
		private Label ratedDifficulty;
		private Image albumCover;
		
		private Table subInformation;
		
		public InformationTable(float width, float height) {
			defaults().center();
			setPosition(scrollPane.getWidth() + scrollPane.getX(), 0);
			setSize(width, height);
			subInformation = new Table(skin);
			albumCover = new Image(assets.get("defaultCover.png", Texture.class));
			songTitle = new ScrollingText("", null, skin, true, true, 1f/screenConfig.getTargetFramesPerSecond());
			author = new Label(null, skin, "sub-font", skin.getColor("default"));
			musicDuration = new Label(null, skin, "sub-font", skin.getColor("default"));
			previousTop = new Label(null, skin, "sub-font", skin.getColor("default"));
			ratedDifficulty = new Label(null, skin, "sub-font", skin.getColor("default"));
		}
		
		public void setDisplayedSelectable(MusicSelectable displayedSelectable) {
			if (displayedSelectable != null) {
				AudioMetadata metadata = displayedSelectable.getMetadata();
				albumCover.setDrawable(new TextureRegionDrawable(displayedSelectable.getAlbumArtTexture()));
				songTitle.setText(metadata.getTitle(), null);
				author.setText(metadata.getAuthor());
				musicDuration.setText(metadata.getDuration());
				//TODO previous top
				//TODO rated difficulty
				beginButton.setDisabled(false);
			} else {
				albumCover.setDrawable(new TextureRegionDrawable(new TextureRegion(assets.get("defaultCover.png", Texture.class))));
				songTitle.setText("loading...", null);
				author.setText("...");
				musicDuration.setText("...");
				previousTop.setText("...");
				ratedDifficulty.setText("...");
			}
		}
		
		public void setToDefault() {
			clear();
			subInformation.clear();
			
			albumCover.setDrawable(new TextureRegionDrawable(new TextureRegion(assets.get("defaultCover.png", Texture.class))));
			add(albumCover).size(getWidth()/2f).spaceBottom(25f);
			row();
			songTitle.setText("...", null);
			add(songTitle).width(getWidth()*0.6f).spaceBottom(30f);
			row();
			author.setText("...");
			author.setEllipsis(true);
			author.setAlignment(Align.center);
			subInformation.add(author).expand();
			subInformation.row();
			musicDuration.setText("...");
			subInformation.add(musicDuration);
			subInformation.row();
			previousTop.setText("...");
			subInformation.add(previousTop);
			subInformation.row();
			ratedDifficulty.setText("...");
			subInformation.add(ratedDifficulty);
			add(subInformation).width(0.4f*getWidth());
			row();
			add(beginButton).spaceTop(20f).fillX();
			beginButton.setDisabled(true);
		}
	}
}
