package me.netux.lookingatsomething;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.LinearGradientPaint;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Line2D;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.Control;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.JComponent;
import javax.swing.JFrame;

@SuppressWarnings("serial")
public class LookingAtSomething extends JFrame {
	
	private final Toolkit defaultTk = Toolkit.getDefaultToolkit();
	private final List<Raindrop> raindrops = new ArrayList<>();
	private final int raindropMinLenght = 40, raindropMaxLenght = 80, raindropMaxOnScreen = 100, raindropFallingVelocity = 20, lightningChange = 1000;
	private final Clip birds = openClip("birds.wav"), rain = openClip("rain.wav"), lightning = openClip("lightning.wav");
	private int state = 0, maxState = 0, dummyRetonance = 0, lightningTimer = 0;
	private boolean showKeys = true, muted = false;
	private Point lastMousePos;
	
	public static void main(String[] args) { new LookingAtSomething(); }
	
	public LookingAtSomething() {
		setTitle("Looking At Something");
//		setSize(defaultTk.getScreenSize());
		setSize(700, 700);
		setDefaultCloseOperation(EXIT_ON_CLOSE);
//		setExtendedState(MAXIMIZED_BOTH);
		setIconImage(defaultTk.getImage(getClass().getResource("observer-resources/icon.png")));
		
		lastMousePos = new Point(getWidth() / 2, 0);
		
		Canvas canvas = new Canvas();
		add(canvas);
		
		setVisible(true);
		
		new Thread(new Runnable() {
			@Override
			public void run() {
				for(int i = 0; i < raindropMaxOnScreen; i++) raindrops.add(new Raindrop(new Point(dice(-200, getWidth() + 200), 0)));
				
				FloatControl birdsVolume = getVolumeValue(birds);
				birdsVolume.setValue(birdsVolume.getMinimum());
				birds.loop(-1);
				
				FloatControl rainVolume = getVolumeValue(rain);
				rainVolume.setValue(rainVolume.getMinimum());
				rain.loop(-1);
				
				while(true) {
//					System.out.println(lastMousePos.y + " / " + (getHeight() / raindropMaxOnScreen));
					state = (lastMousePos.y - 30) / (getHeight() / raindropMaxOnScreen);
					maxState = (getHeight() - 30) / (getHeight() / raindropMaxOnScreen);
					
					if(!muted) {
						float birdsNewVolume = state / (getHeight() / birdsVolume.getMinimum());
						float rainNewVolume = state / (getHeight() / rainVolume.getMinimum());
						
//						birdsVolume.setValue(birdsNewVolume);
						rainVolume.setValue(rainNewVolume);
						
//						System.out.println(birdsNewVolume + " | " + (-rainNewVolume - 5));
					} else {
						birdsVolume.setValue(birdsVolume.getMinimum());
						rainVolume.setValue(rainVolume.getMinimum());
					}
					
					for(int i = 0; i < state; i++) {
						if(i + 1 > raindrops.size()) break;
						if(raindrops.get(i).y1 >= getHeight()) raindrops.remove(i);
						else raindrops.get(i).fall(raindropFallingVelocity);
					}
					if(raindrops.size() != raindropMaxOnScreen) raindrops.add(0, new Raindrop(new Point(dice(0, getWidth()), 0)));
					if(dummyRetonance != 0) {
						if(dummyRetonance < 0) dummyRetonance++;
						else dummyRetonance--;
					}
					
					if(state > raindropMaxOnScreen - 10) {
						if(dice(0, lightningChange) == 0 && lightningTimer <= -100) {
							lightningTimer = 255;
							lightning();
						}
					}
					lightningTimer -= 5;
					
					repaint();
					try { Thread.sleep(10); }
					catch (InterruptedException dummyException) { }
				}
			}
		}).start();
		
		addMouseMotionListener(new MouseMotionListener() {
			
			private void action(MouseEvent e) {
				int center = getWidth() / 2, distance = 0;
				if(e.getPoint().x > center && lastMousePos.x > e.getPoint().x) distance = lastMousePos.x - e.getPoint().x;
				else if(e.getPoint().x < center && lastMousePos.x < e.getPoint().x) distance = lastMousePos.x - e.getPoint().x;
				if(distance > 10 || distance < -10) dummyRetonance += distance / 10;
				
				lastMousePos = e.getPoint();
			}
			
			@Override
			public void mouseMoved(MouseEvent e) { action(e); }
			
			@Override
			public void mouseDragged(MouseEvent e) { action(e); }
			
		});
		
		addKeyListener(new KeyListener() {
			
			@Override
			public void keyPressed(KeyEvent e) {
				if(e.getExtendedKeyCode() == KeyEvent.VK_H) showKeys = !showKeys;
				if(e.getExtendedKeyCode() == KeyEvent.VK_M) muted = !muted;
			}
			
			@Override
			public void keyTyped(KeyEvent e) { }
			
			@Override
			public void keyReleased(KeyEvent e) { }
			
		});
	}
	
	class Canvas extends JComponent {
		
		@Override
		public void paint(Graphics g) {
			Graphics2D g2 = (Graphics2D) g;
			Color top = new Color(0, 205, 250), center = Color.CYAN, bottom = Color.WHITE;
			for(int i = 0; i <= state; i++) {
				int temp;
				
				temp = 3;
				top = new Color(
					top.getRed() - temp <= 0 ? 0 : top.getRed() - temp,
					top.getGreen() - temp <= 0 ? 0 : top.getGreen() - temp,
					top.getBlue() - temp <= 0 ? 0 : top.getBlue() - temp
				);
				
				temp = 4;
				center = new Color(
					center.getRed() - temp <= 0 ? 0 : center.getRed() - temp,
					center.getGreen() - temp <= 0 ? 0 : center.getGreen() - temp,
					center.getBlue() - temp <= 0 ? 0 : center.getBlue() - temp
				);
				
				temp = 5;
				bottom = new Color(
					bottom.getRed() - temp <= 0 ? 0 : bottom.getRed() - temp,
					bottom.getGreen() - temp <= 0 ? 0 : bottom.getGreen() - temp,
					bottom.getBlue() - temp <= 0 ? 0 : bottom.getBlue() - temp
				);
			}
			LinearGradientPaint background = new LinearGradientPaint(
					new Point(getWidth() / 2, state),
					new Point(getWidth() / 2, getHeight() + state),
					new float[] { 0F, .5F, 1F },
					new Color[] { top, center, bottom }
			);
			g2.setPaint(background);
			g2.fillRect(0, 0, getWidth(), getHeight());
			
			g2.setStroke(new BasicStroke(1.5F));
			for(int i = 0; i < state; i++) {
				if(i + 1 > raindrops.size()) break;
				Raindrop r = raindrops.get(i);
				g2.setPaint(new GradientPaint(r.getP1(), new Color(255, 255, 255, 100), r.getP2(), new Color(225, 255, 255, 250)));
				int toAdd = lastMousePos.x / (getWidth() / 15) + (getWidth() / 2);
				toAdd = 0;
				r.x2 += toAdd;
				g2.draw(r);
				r.x2 -= toAdd;
			}
			
			if(lightningTimer > 0) {
				g2.setColor(new Color(255, 255, 255, lightningTimer));
				g2.fillRect(0, 0, getWidth(), getHeight());
			}
			
			g2.setColor(Color.WHITE);
			g2.setFont(new Font("Arial", Font.BOLD, 40));
			g2.drawString(state + " | " + maxState + " | " + lastMousePos.y, 5, 35);
		}
		
	}
	
	class Raindrop extends Line2D.Double {
		
		private int lenght;
		
		public Raindrop(Point from) {
			this.lenght = dice(raindropMinLenght, raindropMaxLenght);
			
			super.x1 = from.x;
			super.y1 = from.y - lenght + 5;
			super.x2 = from.x;
			super.y2 = from.y + lenght;
		}
		
		public void fall(int velocity) {
			super.y1 += velocity;
			super.y2 += velocity;
			
//			if(dummyRetonance > 0) {
//				super.x1 += dummyRetonance;
//				super.x2 += dummyRetonance;
//			}
//			if(dummyRetonance < 0) {
//				super.x1 -= dummyRetonance;
//				super.x2 -= dummyRetonance;
//			}
		}
		
	}
	
	private void lightning() {
		if(muted) return;
		if(lightning.isRunning()) lightning.stop();
		lightning.setFramePosition(0);
		lightning.start();
	}
	
	private Clip openClip(String from) {
		try {
			Clip result = AudioSystem.getClip();
			result.open(AudioSystem.getAudioInputStream(getClass().getResource("observer-resources/" + from)));
			return result;
		} catch (LineUnavailableException | IOException | UnsupportedAudioFileException x) { return null; }
	}
	
	private FloatControl getVolumeValue(Clip from) {
		return (FloatControl) from.getControl(FloatControl.Type.MASTER_GAIN);
	}
	
	private int dice(int min, int max) { return new Random().nextInt(max - min) + min; }
	
}
