package client.gui;

import java.io.File;
import java.nio.file.Files;

import client.Client.FileMode;
import client.audio.MidiDecoder;
import client.audio.MidiHandle;
import client.audio.OPLChip;
import client.audio.OPLChip.EnvState;
import client.audio.OPLChip.ModGen;
import client.audio.OPLChip.OPLChannel;
import client.audio.OPLChip.OPLSlot;
import client.gui.element.ActButton;
import client.gui.element.ButtonCallback;
import client.gui.element.Element;
import client.gui.element.Fill;
import client.gui.element.GuiList;
import client.gui.element.ListEntry;
import client.gui.element.NavButton;
import client.gui.element.PressType;
import client.renderer.Drawing;
import common.log.Log;
import common.rng.Random;
import common.util.Color;

public class GuiPlayer extends GuiList<GuiPlayer.SongEntry> {
	protected class SongEntry implements ListEntry {
		private final File file;

		protected SongEntry(File file) {
			this.file = file;
		}

		public void draw(int x, int y, int width, int height, int mouseX, int mouseY, boolean hovered) {
			Drawing.drawText(this.file.getName(), x + 3, y, GuiPlayer.this.playing && GuiPlayer.this.entry == this ? (GuiPlayer.this.paused != null ? 0xffffff00 : 0xff00ff00) : 0xffffffff);
		}

		public void select(boolean dclick, int mx, int my) {
			if(dclick)
				GuiPlayer.this.select();
		}
	}
	
	private class Visualizer extends Element {
		private static final int[] plr_waveforms = {0xff00ff00, 0xffff0000, 0xff0000ff, 0xff00ffff, 0xffff00ff, 0xffffff00, 0xff80ff00, 0xff00ff80};
		private static final char[] plr_wheel = { '/', '-', '\\', '|'};
		
		public Visualizer(int x, int y, int w, int h) {
			super(x, y, w, h, null);
		}
		
		public boolean canHover() {
			return false;
		}
		
		protected int getMarginX() {
			return 0;
		}
		
		protected int getMarginY() {
			return 0;
		}
		
		private int gui_render_bar(int x, int y, int w, int h, int top, int bottom, int bg, int v) {
			v = (v < 0) ? 0 : v;
			Drawing.drawGradient(this.pos_x + x, this.pos_y + y, w, h, 0xff000000 | top, 0xff000000 | bottom);
			if(++v < h)
				Drawing.drawRect(this.pos_x + x, this.pos_y + y, w, h - v, 0xff000000 | bg);
			return h;
		}

		private int gui_render_grad(int x, int y, int w, int h, int top, int bottom) {
			Drawing.drawGradient(this.pos_x + x, this.pos_y + y, w, h, 0xff000000 | top, 0xff000000 | bottom);
			return h;
		}

		private int gui_render_rect(int x, int y, int w, int h, int color) {
			Drawing.drawRect(this.pos_x + x, this.pos_y + y, w, h, 0xff000000 | color);
			return h;
		}

		private int gui_render_text(int x, int y, int color, String text) {
			Font.set(Font.SMALL);
			Drawing.drawText(text, this.pos_x + x, this.pos_y + y, 0xff000000 | color);
			Font.unset();
			return Font.SMALL.getHeight();
		}

		protected void drawForeground(int x1, int y1, int x2, int y2) {
			gui_render_grad(0, 0, this.size_x, this.size_y, this.gm.style.field_btm, this.gm.style.field_top);
			if(!this.gm.midiVisualizer)
				return;
			MidiDecoder midi = this.gm.getAudioInterface().alGetMidi();
			if(midi == null)
				return;
			OPLChip chip = midi.chip;
			MidiHandle bank = midi.bank;
			try {
				int nch = chip.channel.length * 2;
				int bx = this.size_x / 2 - (nch * 3 + 1) / 2;
				int by = this.size_y / 2 - (349 + 10 * 4) / 2;
				int x = bx;
				int y = by;
				
				y += 10;
				y += gui_render_rect(x, y, nch * 3 + 1, 113, 0x3f0000) + 10;
				y += gui_render_rect(x, y, nch * 3 + 1, 116, 0x003f00) + 10;
				y += gui_render_rect(x, y, nch * 3 + 1, 116, 0x00003f) + 10;
				y += gui_render_rect(x, y, nch * 3 + 1, 4, 0x2f2f2f);
				for(int c = 0; c < nch; c++) {
					OPLChannel channel = chip.channel[c / 2];
					x = bx + 1 + 3 * c;
					y = by + 1;
					ModGen[] out = channel.out;
					int accm = 0;
					for(int z = 0; z < out.length; z++) {
						accm += out[z].getModulation();
					}
					int chnlvl = (short)((accm * channel.level[c % 2]) >> 16);
					chnlvl = chnlvl < 0 ? (-chnlvl) : chnlvl;
					chnlvl = (chnlvl * 16) / 2048;
					chnlvl = chnlvl > 32 ? 32 : chnlvl;
					int freq = (int)(((float)channel.slots[0].read_freq) / 6210000.0f * 64.0f);
					y += 10;
					y += gui_render_bar(x, y, 2, 32, 0xff0000, 0x00ff00, 0x000000, chnlvl) + 1;
					y += gui_render_rect(x, y, 2, 2, ((channel.slots[0].eg_out <= 0x100) || (channel.slots[1].eg_out <= 0x100)) ? 0x00ff00 : 0x000000) + 1;
					y += gui_render_rect(x, y, (c % 2) == 0 ? 3 : 2, 2, (channel.read_algo & 0x04) != 0 ? 0xff00ff : 0x000000) + 1;
					y += gui_render_grad(x, y, 2, 2, (channel.read_algo & (c % 2 == 0 ? 0x01 : 0x02)) != 0 ? 0x00ff00 : 0x808080, 0x0000ff) + 1;
					y += gui_render_bar(x, y, 2, 4, 0x00ff00, 0xffff00, 0x000000, (c % 2 == 0 ? channel.read_fb1 : channel.read_fb2) / 2) + 1;
					y += gui_render_bar(x, y, 2, 64, 0x707070, 0x505050, 0x000000, freq) + 1;
					gui_render_rect(x, y - 65 + (63 - freq), 2, 1, 0xff0000);
					for(int o = 0; o < 2; o++) {
						OPLSlot slot = channel.slots[(c % 2) * 2 + o];
						y += 1;
						y += 10;
						y += gui_render_rect(x, y, 2, 2, slot.key ? 0x00ff00 : 0x000000) + 1;
						y += gui_render_rect(x, y, 2, 2, slot.trem != chip.zeromod ? 0x5fdf00 : 0x000000);
						y += gui_render_rect(x, y, 2, 2, slot.reg_vib ? 0xffff00 : 0x000000);
						y += gui_render_rect(x, y, 2, 2, slot.reg_type ? 0xff0000 : 0x000000);
						y += gui_render_rect(x, y, 2, 2, slot.reg_ksr ? 0x5f00bf : 0x000000) + 1;
						y += gui_render_bar(x, y, 2, 8, 0xff0000, 0xffff00, 0x000000, slot.reg_mult / 2) + 1;
						y += gui_render_bar(x, y, 2, 32, 0xff0000, 0x00ff00, 0x000000, (63 - slot.reg_tl) / 2) + 1;
						y += gui_render_bar(x, y, 2, 2, 0x7f00ff, 0x3f00ff, 0x000000, slot.reg_ksl / 2) + 1;
						y += gui_render_rect(x, y, 2, 4, 0x000000) + 1;
						gui_render_rect(x, y - 5 + (7 - slot.reg_wf) / 2, 2, 1, plr_waveforms[slot.reg_wf]);
						
						y += gui_render_bar(x, y, 2, 8, slot.eg_gen == EnvState.ATTACK ? 0xff0000 : 0xaf0000, slot.eg_gen == EnvState.ATTACK ? 0x00ffff : 0x00afaf, 0x000000, slot.reg_ar / 2) + 1;
						y += gui_render_bar(x, y, 2, 8, slot.eg_gen == EnvState.DECAY ? 0xff0000 : 0xaf0000, slot.eg_gen == EnvState.DECAY ? 0x00ffff : 0x00afaf, 0x000000, slot.reg_dr / 2) + 1;
						y += gui_render_bar(x, y, 2, 8, slot.eg_gen == EnvState.SUSTAIN ? 0xff0000 : 0xaf0000, slot.eg_gen == EnvState.SUSTAIN ? 0x00ffff : 0x00afaf, 0x000000, ((slot.reg_sl > 15) ? 0 : (15 - slot.reg_sl)) / 2) + 1;
						y += gui_render_bar(x, y, 2, 8, slot.eg_gen == EnvState.RELEASE ? 0xff0000 : 0xaf0000, slot.eg_gen == EnvState.RELEASE ? 0x00ffff : 0x00afaf, 0x000000, slot.reg_rr / 2) + 1;
						y += gui_render_bar(x, y, 2, 16, 0xff0000, 0x0000ff, 0x000000, (0x1ff - slot.eg_rout) / 32) + 1;
					}
					y += 1;
					y += 10;
					gui_render_rect(x, y, 2, 2, bank.index == c / 2 ? 0x00ffff : 0x000000);
				}
				
				x = bx - 68;
				y = by;
				if(midi.tracks != null) {
					long time = midi.time / 1000L;
					gui_render_text(x, y + 10, 0xffffff, String.format("%02d:%02d.%03d", time / 1000L / 60L % 60L, time / 1000L % 60L, time % 1000L));
					gui_render_text(x, y + 10 + 9, 0xffffff, String.format("%c%c%c%c %c", 
						(midi.tpqn != 0 && (((midi.tick / midi.tpqn) & 3) == 0)) ? '*' : '.', 
						(midi.tpqn != 0 && (((midi.tick / midi.tpqn) & 3) == 1)) ? '*' : '.', 
						(midi.tpqn != 0 && (((midi.tick / midi.tpqn) & 3) == 2)) ? '*' : '.', 
						(midi.tpqn != 0 && (((midi.tick / midi.tpqn) & 3) == 3)) ? '*' : '.', 
						plr_wheel[(midi.tick >> 5) & 3]));
					gui_render_text(x, y + 30, 0xffffff, Color.NEON + "Tick/Pos.");
					gui_render_text(x, y + 30 + 9, 0xffffff, String.format("%d", midi.tick));
					gui_render_text(x, y + 50, 0xffffff, Color.NEON + "Tempo BPM");
					gui_render_text(x, y + 50 + 9, 0xffffff, String.format("%d +%dQ", midi.uspb != 0 ? (60000000 / midi.uspb) : 0, midi.tpqn));
				}
				gui_render_text(x, y + 70, 0xffffff, Color.NEON + "Aktiv");
				gui_render_text(x, y + 70 + 9, 0xffffff, String.format("%d / %d", bank.used, bank.voices.length));
				x = bx;
				gui_render_text(x, y, 0xffffff, "Kanal");
				gui_render_text(x, y + 10 + 113, 0xffffff, "Operator 1 + 3 / Modulator");
				gui_render_text(x, y + 20 + 113 + 116, 0xffffff, "Operator 2 + 4 / Träger");
				gui_render_text(x, y + 30 + 113 + 116 * 2, 0xffffff, "Stimmen-Zeiger");
				x += nch * 3 + 2;
				y += 10;
				gui_render_text(x, y + 0, 0xffffff, "Pegel");
				gui_render_text(x, y + 35, 0xffffff, "Modus/Al.");
				gui_render_text(x, y + 42, 0xffffff, "Feedback");
				gui_render_text(x, y + 49, 0xffffff, "Frequenz");
				y += 113 + 10;
				for(int o = 0; o < 2; o++) {
					gui_render_text(x, y + 3, 0xffffff, "T-V-S-K");
					gui_render_text(x, y + 12, 0xffffff, "Frq.-Mult.");
					gui_render_text(x, y + 21, 0xffffff, "Op.-Pegel");
					gui_render_text(x, y + 48, 0xffffff, "Tst.-Skale");
					gui_render_text(x, y + 55, 0xffffff, "Wellenf.");
					gui_render_text(x, y + 62, 0xffffff, "Attack");
					gui_render_text(x, y + 71, 0xffffff, "Decay");
					gui_render_text(x, y + 80, 0xffffff, "Sustain");
					gui_render_text(x, y + 89, 0xffffff, "Release");
					gui_render_text(x, y + 98, 0xffffff, "Hüllkurve");
					y += 116 + 10;
				}
			}
			catch(Throwable e) {
			}
		}
	}

	public static enum RepeatMode {
		REPEAT_OFF("N"),
		REPEAT_SINGLE("L"),
		REPEAT_ALL("A"),
		REPEAT_SHUFFLE("R");
		
		private final String display;
		
		private RepeatMode(String display) {
			this.display = display;
		}
	};

	public static final GuiPlayer INSTANCE = new GuiPlayer();
	
	private final Random rand = new Random();
	private MidiDecoder paused;
	private int position = -1;
	private SongEntry entry;
	private boolean playing;
	private RepeatMode mode = RepeatMode.REPEAT_OFF;
	private ActButton playButton;
	private ActButton stopButton;
	private ActButton modeButton;
	
	private GuiPlayer() {
	}
	
	private void updateDisplay() {
		if(this.gm.open != this)
			return;
		this.playButton.setText(!this.elements.isEmpty() && this.playing && this.paused == null ? "=" : "*");
		this.modeButton.setText(this.mode.display);
	}
	
	public void playMidi(int index) {
		this.paused = null;
		this.playing = true;
		this.entry = this.getListEntry(this.position = index);
		this.updateDisplay();
		File file = this.entry.file;
		byte[] data;
		try {
			data = Files.readAllBytes(file.toPath());
		}
		catch(Throwable e) {
			Log.SOUND.error(e, "Konnte Datei '%s' nicht laden", file);
			return;
		}
		MidiDecoder midi;
		try {
			midi = new MidiDecoder(data, this.gm.midiNoWait, this.gm.midiVoices, this.gm.midiBank, this.gm.midiKeep, this.gm.midiUnknown, this.gm.midiVelocity);
		}
		catch(Throwable e) {
			Log.SOUND.error(e, "Konnte MIDI '%s' nicht laden", file);
			return;
		}
		midi.setDebug(this.gm.midiDebug);
		this.gm.getAudioInterface().alMidi(midi);
	}
	
	public void test(int start, int end, int delay, int note) {
		this.paused = null;
		this.playing = false;
		this.position = -1;
		this.gm.getAudioInterface().alMidi(new MidiDecoder(this.gm.midiBank, start, end, delay, note));
		this.updateDisplay();
	}
	
	public void clear() {
		this.stop();
		this.elements.clear();
		this.setSelected(-1);
		this.updateDisplay();
	}
	
	public void remove() {
		if(this.selectedElement >= 0) {
			this.elements.remove(this.selectedElement);
			if(this.playing && this.position == this.selectedElement) {
				if(this.position == this.elements.size())
					this.stop();
				else
					this.playMidi(this.position);
			}
			if(this.elements.isEmpty())
				this.setSelected(-1);
			else if(this.selectedElement >= this.elements.size())
				this.setSelected(this.elements.size() - 1);
			this.updateDisplay();
		}
	}
	
	public void select() {
		if(this.selectedElement >= 0)
			this.playMidi(this.selectedElement);
	}
	
	public void random() {
		if(!this.elements.isEmpty())
			this.playMidi(this.rand.zrange(this.elements.size()));
	}
	
	public void prev() {
		if(!this.elements.isEmpty())
			this.playMidi(this.position <= 0 ? this.elements.size() - 1 : this.position - 1);
	}
	
	public void next() {
		if(!this.elements.isEmpty())
			this.playMidi(this.position >= this.elements.size() - 1 ? 0 : this.position + 1);
	}
	
	public void mode(int dir) {
		this.mode = dir == 0 ? RepeatMode.REPEAT_OFF : RepeatMode.values()[(RepeatMode.values().length + this.mode.ordinal() + dir) % RepeatMode.values().length];
		this.updateDisplay();
	}
	
	private static boolean isMidiFile(File file) {
		String name = file.getName().toLowerCase();
		return name.endsWith(".mid") || name.endsWith(".midi") || name.endsWith(".kar");
	}
	
	public void add(File file) {
		if(file.isDirectory()) {
			File[] files = file.listFiles(sub -> sub.isDirectory() || GuiPlayer.isMidiFile(sub));
			if(files != null) {
				for(File sub : files) {
					this.add(sub);
				}
			}
		}
		else if(isMidiFile(file)) {
			this.elements.add(new SongEntry(file));
		}
	}
	
	public void stop() {
		this.paused = null;
		this.playing = false;
		this.position = -1;
		this.gm.getAudioInterface().alMidi(null);
		this.updateDisplay();
	}
	
	public void play() {
		MidiDecoder midi = this.gm.getAudioInterface().alGetMidi();
		if(midi != null) {
			this.gm.getAudioInterface().alMidi(null);
			this.paused = midi;
		}
		else {
			this.playing = true;
			if(this.paused != null) {
				this.paused.setDebug(this.gm.midiDebug);
				this.gm.getAudioInterface().alMidi(this.paused);
			}
			this.paused = null;
		}
		this.updateDisplay();
	}

	public void updatePlayer() {
		if(!this.elements.isEmpty() && this.playing && this.paused == null && this.gm.getAudioInterface().alGetMidi() == null) {
			if(this.position >= 0) {
				switch(this.mode) {
					case REPEAT_OFF:
						this.position += 1;
						break;
					case REPEAT_SINGLE:
						break;
					case REPEAT_ALL:
						if((this.position += 1) >= this.elements.size())
							this.position = 0;
						break;
					case REPEAT_SHUFFLE:
						this.position = this.rand.zrange(this.elements.size());
						break;
				}
			}
			else {
				this.position = 0;
			}
			if(this.position >= this.elements.size()) {
				this.playing = false;
				this.position = -1;
				this.updateDisplay();
			}
			else {
				this.playMidi(this.position);
			}
		}
	}

	public void init(int width, int height) {
    	super.init(width, height);
        this.setDimensions(360, height, 18, height - 18);

		this.add(new NavButton(0, 0, 60, 18, GuiMenu.INSTANCE, "Zurück"));
        this.add(new Fill(60, 0, 142, 18, "OPL MIDI Player"));
		this.addSelector("mid_visualizer", 202, 0, 158, 18);

		this.playButton = this.add(new ActButton(0, height - 18, 18, 18, new ButtonCallback() {
			public void use(ActButton elem, PressType action) {
				GuiPlayer.this.play();
			}
		}, "*"));
		this.stopButton = this.add(new ActButton(18, height - 18, 18, 18, new ButtonCallback() {
			public void use(ActButton elem, PressType action) {
				GuiPlayer.this.stop();
			}
		}, "#"));
		this.modeButton = this.add(new ActButton(36, height - 18, 18, 18, new ButtonCallback() {
			public void use(ActButton elem, PressType action) {
				GuiPlayer.this.mode(action == PressType.TERTIARY ? 0 : (action == PressType.SECONDARY ? -1 : 1));
			}
		}, ""));
		this.add(new ActButton(54, height - 18, 18, 18, new ButtonCallback() {
			public void use(ActButton elem, PressType action) {
				GuiPlayer.this.prev();
			}
		}, "<"));
		this.add(new ActButton(72, height - 18, 18, 18, new ButtonCallback() {
			public void use(ActButton elem, PressType action) {
				GuiPlayer.this.next();
			}
		}, ">"));
		this.add(new ActButton(90, height - 18, 18, 18, new ButtonCallback() {
			public void use(ActButton elem, PressType action) {
				GuiPlayer.this.random();
			}
		}, "?"));
		this.add(new ActButton(108, height - 18, 18, 18, new ButtonCallback() {
			public void use(ActButton elem, PressType action) {
				GuiPlayer.this.select();
			}
		}, "~"));
		this.add(new ActButton(126, height - 18, 18, 18, new ButtonCallback() {
			public void use(ActButton elem, PressType action) {
				GuiPlayer.this.remove();
			}
		}, "-"));
		this.add(new ActButton(144, height - 18, 18, 18, new ButtonCallback() {
			public void use(ActButton elem, PressType action) {
				GuiPlayer.this.clear();
			}
		}, "X"));
		
		this.addSelector("mid_opl_bank", 162, height - 18, 162, -18);
		this.add(new ActButton(324, height - 18, 18, 18, new ButtonCallback() {
			public void use(ActButton elem, PressType action) {
				GuiPlayer.this.gm.showFileDialog(FileMode.FILE_LOAD_MULTI, "MIDIs laden", null, new FileCallback() {
					public void selected(File file) {
						GuiPlayer.this.add(file);
					}
				});
			}
		}, "+"));
        this.add(new ActButton(342, height - 18, 18, 18, new ButtonCallback() {
			public void use(ActButton elem, PressType action) {
				GuiPlayer.this.gm.showFileDialog(FileMode.DIRECTORY_LOAD, "MIDIs laden", null, new FileCallback() {
					public void selected(File file) {
						GuiPlayer.this.add(file);
					}
				});
			}
		}, "^"));
//		this.add(new ActButton(0, 130, 180, 0, new ButtonCallback() {
//			public void use(ActButton elem, PressType action) {
//				GuiPlayer.this.test(0, 255, 500, 36);
//			}
//		}, "MIDI-Bank testen"));
        this.add(new Visualizer(360, 0, width - 360, height));
		
		this.updateDisplay();
	}

	public String getTitle() {
		return "MIDI-Player";
	}

	public int getSlotHeight() {
		return 20;
	}
	
	/*
#define SYM_PLAY "\U00100000"
#define SYM_STOP "\U00100001"
#define SYM_PAUSE "\U00100002"
#define SYM_NEXT "\U00100003"
#define SYM_PREV "\U00100004"
#define SYM_FFORWARD "\U00100005"
#define SYM_FREVERSE "\U00100006"
#define SYM_CONTINUOS "\U00100007"
#define SYM_REPEAT "\U00100008"
#define SYM_LOOPED "\U00100009"
#define SYM_SHUFFLE "\U0010000a"
#define SYM_PRANDOM "\U0010000b"
#define SYM_JUMPTO "\U0010000c"
#define SYM_FOLDER "\U0010000d"
#define SYM_DELETE "\U0010000e"
#define SYM_SAVEDISK "\U0010000f"

void gui_fmt_velofunc(gui_t *elem, int value) {
	snprintf(elem.text, elem.capacity, "%s %d: %s", elem.format_text, value, (value ? (value == -128 ? STR_MID_VELO_ONE : (value < 0 ? STR_MID_VELO_ATTN : STR_MID_VELO_LOG)) : STR_MID_VELO_LIN));
}

void gui_plr_update_text(window_t *win) {
	gui_t *elem = gui_get(win, 17);
	const char *name = "";
	int pos = (snd.mid_queue && (snd.mid_playpos != INT_MIN)) ? ((snd.mid_playpos < -1) ? (-(snd.mid_playpos) - 3) : snd.mid_playpos) : -1;
	if(pos >= 0) {
		if(pos != (snd.mid_queued - 1))
			snd.mid_queue[pos+1][-1] = 0;
		if(name = strrchr(&(snd.mid_queue[pos])[6], '/'))
			name += 1;
		else
			name = &(snd.mid_queue[pos])[6];
		snprintf(elem.text, elem.capacity, "%s: %d/%d - %s", STR_PLR_INFO, pos + 1, snd.mid_queued, name);
		if(pos != (snd.mid_queued - 1))
			snd.mid_queue[pos+1][-1] = '\n';
	}
	else {
		snprintf(elem.text, elem.capacity, "%s", STR_PLR_INFO);
	}
	gui_update_text(elem);
}

void gui_plr_update(window_t *win) {
	gui_t *elem = gui_get(win, 2);
	snprintf(elem.text, elem.capacity, snd.mid_queue && (snd.mid_playpos >= -1) ? COL_GREEN SYM_PLAY : SYM_PLAY);
	gui_update_text(elem);
	elem = gui_get(win, 3);
	snprintf(elem.text, elem.capacity, !snd.mid_queue || (snd.mid_playpos == INT_MIN) ? COL_RED SYM_STOP : SYM_STOP);
	gui_update_text(elem);
	elem = gui_get(win, 4);
	snprintf(elem.text, elem.capacity, snd.mid_queue && (snd.mid_playpos != INT_MIN) && (snd.mid_playpos < -1) ? COL_YELLOW SYM_PAUSE : SYM_PAUSE);
	gui_update_text(elem);
	gui_plr_update_text(win);
}
*/
}
