package me.netux.dittokonga;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Desktop;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
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.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.JFrame;
import javax.swing.JPanel;

@SuppressWarnings("serial")
public class DittoKonga extends JFrame {

	private static List<Ditto> dittos; // List with all the current Ditto's on the screan
	private static Board b; // The place where all the Ditto's will be drawed
	private static Clip konga; // Konga played in the background
	private static Color bgColor, bgColor2; // The background colors
	
	public static void main(String[] args) {
		dittos = new ArrayList<>(); // Defining the list of Ditto's
		new DittoKonga(); // Opening the windows
	}
	
	public DittoKonga() {
		final Toolkit tk = Toolkit.getDefaultToolkit(); // Getting some useful stuff
		setTitle("Konga!"); // Basic: We set the title to "Konga!"
		setDefaultCloseOperation(EXIT_ON_CLOSE); // When closed it will actually close the program
		setExtendedState(MAXIMIZED_BOTH); // Make it maximized
		setSize(tk.getScreenSize().width, Toolkit.getDefaultToolkit().getScreenSize().height); // For some reason if the windows is Maximized I like to set it's size to the size of the screen.
		
		if(!restartKonga()) { // We try to play the music, if it doesn't work then...
			System.exit(0); // ... we close the program
			return; // ... and stop it, so it doesn't do any extra stuff
		}
		
		b = new Board(); /* Generating and   */
		add(b);			 /* adding the board */
		
		addKeyListener(new KeyListener() { // Checking for Key pressing and such
			
			@Override
			public void keyPressed(KeyEvent e) { // Listening for when the key is pressed
				if(e.getExtendedKeyCode() == 116) { // If key is equals to F5 then...
					dittos.clear(); // ... remove all the Ditto's
					repaint(); // ... clear the screen and redraw everything
					restartKonga(); // and restart the music
				}
				
				if(e.getExtendedKeyCode() == 87) { // If the key is equals to 'W'
					try { Desktop.getDesktop().browse(new URI("http://www.matmartinez.net/nsfw/")); } // ... open the browser with the URL of the original website!
					catch (IOException | URISyntaxException x) { } // just because it can fail we handle that
					System.exit(0); // Finally we close all this
				}
			}
			
			@Override
			public void keyTyped(KeyEvent e) { } // We won't be needing this right now but Java is a bitch so we need to add it too u_u
			
			@Override
			public void keyReleased(KeyEvent e) { } // We won't be needing this neither
			
		});
		
		new Thread(new Runnable() { // Starting a new "timer" to change the color of the backgrounds and add Ditto's
			@Override
			public void run() { // This will be executed once we start the timer
				while(true) { // It will run forever, but will stop for some miliseconds and then continue
					bgColor = randomColor(); // Making a new random color and updating the first background to it.
					bgColor2 = randomColor(); // Making a second random color and updating the second background to it.
					
					if(dittos.size() < 5) dittos.add(new Ditto()); // If the sum of all the Ditto's on screen is lower than 5 then add a new Ditto
					
					try { Thread.sleep(400); } // Stoping the timer for 400 miliseconds ( 4/10 of a second )
					catch (InterruptedException e) { } // to do this Java launches an error, we just handle it with nothing inside
				}
			}
		}).start(); // Start the timer, it will run until the program stops
		
		new Thread(new Runnable() { // Starting another timer, this time to update the coordinates and frames of the Ditto's
			@Override
			public void run() { // Again this will be executed when the timer starts
				while(true) { // Again this will run forever but stop for some miliseconds and blablabla...
					b.hero.update(); // Lets first update all the stuff on our mayor Ditto: the Hero.
					setIconImage(tk.getImage("konga-resources/ditto" + b.hero.frame + ".png")); // Lets also change the icon so it is syncronized with the Hero
					
					List<Ditto> dittosCopy = new ArrayList<>(); // Lets do a second list with all the Ditto's, just for security.
					dittosCopy.addAll(dittos); // Now we add all the Ditto's from the original list to the new list
					for(Ditto d : dittosCopy) { // Goes thru all the Ditto's on that new list and...
						if(!d.isVisible()) dittos.remove(d); // First, if the ditto we are looking at is not visible then remove it
						else d.update(); // But if it is indeed visible then we update it's coordinates and frame
					}
					
					repaint(); // Now we clear and redraw the screen
					
					try { Thread.sleep(100); } // Again this stops the timer for some time, this one will stop it for 100 miliseconds ( 1/10 of a second )
					catch (InterruptedException e) { } // Agaiiiiiiin Java launches this error, just handle it with emptyness and we'll be ok c:
				}
			}
		}).start(); // at the end we start the timer
		
		setVisible(true); // aaaaaaaaaaaaaaaaaaaaaaand at the end we just make everything appear on the screen.
	}
	
	class Board extends JPanel { // This is a thing I like to call "Board", is basically a place where you can handle everything that happends on the screen
		
		private Ditto hero; // Lets first say we need a frozen ditto, this ditto will be the mayor ditto and we will call it "Hero"
		
		public Board() { hero = new Ditto(); } // When we generate the board we want to also create the Hero! Thats what we are doing here.
		
		@Override
		public void paint(Graphics g) { // Now lets get whenever the program clears and redraws we want to do something...
			Graphics2D g2 = (Graphics2D) g; // First of all when this happends it gives us something called Graphics, I can draw using this. There is also a better thing called Graphics2D which just adds some possibilities
			Toolkit toolKit = Toolkit.getDefaultToolkit(); // Lets get a copy of the Toolkit; a thing that gives us useful stuff.
			g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // Updating the Graphics so it has Anti-aliasing on
			
			g2.setColor(bgColor); // To draw we first need to define some options, in this case the color. We are going to draw the background now so we will use our background colors
			g2.fillRect(0, 0, getWidth(), getHeight()); // First we draw a rectangle which fits on all the screen
			g2.setColor(bgColor2); // Now we change the color to the second background...
			g2.fillRoundRect(25, 25, getWidth() - 50, getHeight() - 50, 15, 15); // ...and draw the second background.
			/* The program goes from the top to the bottom so it will draw stuff on top of each other */
			
			/* First we draw the Ditto's on the back */
			for(Ditto d : dittos) { // Went thru all the Ditto's ...
				if(!d.behind) continue;
				g2.setComposite(AlphaComposite.SrcOver.derive(d.opacity)); // ... defining the option of opacity of the Graphic to the opacity that the ditto has
				g2.drawImage(toolKit.getImage(getClass().getResource("konga-resources/ditto" + d.frame + ".png")), d.x, d.y, d.w, d.h, null); // and we draw the ditto depending on it's current frame.
			}
			
			/* Then we draw the Hero that appears always on the screen */
			Image theHero = toolKit.getImage(getClass().getResource("konga-resources/ditto" + hero.frame + ".png")); // First we get the usual ditto image depending on the frame the Hero is
			g2.setComposite(AlphaComposite.SrcOver.derive(1.0F)); // lets restart the option of opacity of the Graphic so the Hero appears OK
			g2.drawImage(theHero, (getWidth() / 2) - 250, (getHeight() / 2) - 220, null); // Now we try to draw it on the center of the screen
			
			/* After that we draw the other Ditto's */
			for(Ditto d : dittos) { // Went thru all the Ditto's ...
				if(d.behind) continue;
				g2.setComposite(AlphaComposite.SrcOver.derive(d.opacity)); // ... defining the option of opacity of the Graphic to the opacity that the ditto has
				g2.drawImage(toolKit.getImage(getClass().getResource("konga-resources/ditto" + d.frame + ".png")), d.x, d.y, d.w, d.h, null); // and we draw the ditto depending on it's current frame.
			}
			
			/* Finally we also want add some text information */
			g2.setColor(Color.BLACK); // Lets first restart the option of color to Black
			g2.setFont(new Font("Verdana", Font.PLAIN, 15)); // Now we add a font, this font will be used when we draw the text.
			g2.setComposite(AlphaComposite.SrcOver.derive(1.0F)); // Also restart the opacity
			g2.drawString("W: Go to original webpage", getX(), getHeight() - 20); /* Finally we draw the first */
			g2.drawString("F5: Restart", getX(), getHeight() - 5);				  /* and second text           */
		}
		
	}
	
	class Ditto { // This will represent a ditto. What that means is that it will store all the data of a ditto

		private int x, y, w, h, speed, frame; // We define that we want an X and Y position, as well as a custom Width and Height. Other things are the speed and the current frame
		private float opacity; // Lets also say we want a random opacity
		private boolean toRight, behind; // Other thing is the direction of the ditto; remember they appear from the sides of the screen
		
		public Ditto() { // Below this is all what will happend inside of a ditto when it is first generated
			this.toRight = new Random().nextBoolean(); // We want to define the direction
			this.behind = new Random().nextBoolean(); // I wanted to add this little and dummy feature to make it be behind the Hero or not
			this.speed = (new Random().nextInt(6) + 1) * 50; // ...also the speed, this goes from 1 to 6 and then it gets multiplied by 50.
			this.opacity = new Random().nextFloat(); // We then make a random opacity...
			this.opacity -= this.opacity - 0.1F < 0 ? 0 : 0.1; // ... but we don't want to have an opacity of none so we fix that
			
			int dummy = new Random().nextInt(440); // This will be used for the Width and Height generator
			this.w = dummy + 250; // We first define the Witdh
			this.h = dummy + 220; // ... and then the Height
			
			/* Finally we define the X and Y coords */
			this.x = toRight ? 0 - w : getWidth(); // The X will variate depending on to where the ditto will go
			int randomY = new Random().nextInt(getHeight()); // This will be used to help defining a Y coordinate where the ditto will be fully visible
			this.y = randomY < 0 + h ? 0 : randomY - h; // finally we define the Y.
		}
		
		public void update() { // We will do everything below when we update the ditto:
			if(toRight) this.x += speed; // If the ditto goes to the right then we want to increase the X coord of the ditto by it's speed
			else this.x -= speed; // but if the ditto goes left then we want to decrease it
			
			this.frame++; // Then we update it's frame
			if(frame > 3) frame = 0; // If there is no frame after the third one then we want to set the frame to zero
		}
		
		public boolean isVisible() { return !(x > getWidth() || x < 0 - w); } // Now we check if the ditto is fully invisible on the screen.

	}
	
	public static Color randomColor() { // This will generate a random color so we can use it to change the Backgrounds
		return new Color((int) Math.floor(Math.random() * 255), (int) Math.floor(Math.random() * 255), (int) Math.floor(Math.random() * 255));
	}
	
	private boolean restartKonga() { // This will restart the Konga
		try { // This can fail, so we want to handle it
			if(konga == null) konga = AudioSystem.getClip(); // If there is no Konga defined, we want to define it
			konga.close(); // Just in case, to restart something first you need to stop it
			konga.flush(); // I... actually don't remember what this does, but it helps
			konga.open(AudioSystem.getAudioInputStream(getClass().getResource("konga-resources/konga.wav"))); // Now lets grab the music...
			konga.loop(Clip.LOOP_CONTINUOUSLY); // ... set it to loop forever...
			konga.start(); // ... and start it
			return true; // We say that everything went great
		} catch (LineUnavailableException | IOException | UnsupportedAudioFileException x) { return false; } // we finally handle it saying that something went wrong
	}
	
}
