package com.stereodustparticles.mooler_caster_console;

// SDP Mooler Caster Console
// Ridiculously simple radio DJ program, with a 16-slot soundboard and 2-deck music player
// Written by Ben A. (IfYouLikeGoodIdeas)

// This class defines the spot loader window

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;

import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.ButtonGroup;
import javax.swing.DefaultListModel;
import javax.swing.JTabbedPane;
import javax.swing.JLabel;

public class SpotLoader extends JDialog implements ActionListener {
	// Serial Version UID, to make Java quit complaining
	private static final long serialVersionUID = 1L;
	
	// Variables for controls that change
	private DefaultListModel<String> adsList = new DefaultListModel<String>();
	private DefaultListModel<String> otherMediaList = new DefaultListModel<String>();
	private JTextField localFileName;
	private JLabel spotTitle;
	private JLabel spotCreator;
	private JLabel spotLength;
	
	// The Spot we're working with
	private Spot spot;
	
	// The lists obtained from the SDP server
	private ArrayList<SDPSpotInfo> ads = new ArrayList<SDPSpotInfo>();
	private ArrayList<SDPSpotInfo> otherMedia = new ArrayList<SDPSpotInfo>();
	
	// The current selection from the lists
	private SDPSpotInfo chosenSpot = null;
	
	// Other variables
	private boolean loadFromSDP = false;
	private boolean sdpLoadInProgress = false;

	/**
	 * Create the dialog.
	 */
	public SpotLoader(Spot thisSpot) {
		// Store the passed-in Spot
		this.spot = thisSpot;
		
		// Set dialog properties
		setResizable(false);
		setTitle("Load Spot");
		setModalityType(ModalityType.APPLICATION_MODAL);
		setModal(true);
		setBounds(100, 100, 440, 540);
		getContentPane().setLayout(null);
		
		// NOTE: The order of definitions up here is intentional
		// It may seem out of order, but it needs to be done this way for enable/disable to work!
		
		// SDP spot selection tabs
		JTabbedPane sdpGroups = new JTabbedPane(JTabbedPane.TOP);
		sdpGroups.setBounds(70, 129, 300, 250);
		getContentPane().add(sdpGroups);
		
		// SDP Ads List
		JList<String> adsJList = new JList<String>(adsList);
		adsJList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		adsJList.addListSelectionListener(new ListSelectionListener() {
			public void valueChanged(ListSelectionEvent evt) {
				// Check if an actual spot is selected
				// (fixes issue where this gets called and fails during refresh of the lists - apparently clearing the list counts as a change!)
				if ( adsJList.getSelectedIndex() != -1 ) {
					chosenSpot = ads.get(adsJList.getSelectedIndex());
				}
				// If nothing is selected, reset to dummy display
				else {
					chosenSpot = null;
				}
				updateSpotInfoDisplay();
			}
		});
		JScrollPane adsScrollPane = new JScrollPane(); // Scroll Pane allows for the lists to have scroll bars
		adsScrollPane.setViewportView(adsJList);
		sdpGroups.addTab("Ads", null, adsScrollPane, null);
		
		// SDP Other Media List
		JList<String> otherMediaJList = new JList<String>(otherMediaList);
		otherMediaJList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		otherMediaJList.addListSelectionListener(new ListSelectionListener() {
			public void valueChanged(ListSelectionEvent evt) {
				if ( otherMediaJList.getSelectedIndex() != -1 ) {
					chosenSpot = otherMedia.get(otherMediaJList.getSelectedIndex());
				}
				// If nothing is selected, reset to dummy display
				else {
					chosenSpot = null;
				}
				updateSpotInfoDisplay();
			}
		});
		JScrollPane omScrollPane = new JScrollPane();
		omScrollPane.setViewportView(otherMediaJList);
		sdpGroups.addTab("Other Media", null, omScrollPane, null);
		
		// Local file name text box
		localFileName = new JTextField();
		localFileName.setBounds(35, 55, 270, 20);
		getContentPane().add(localFileName);
		localFileName.setColumns(10);
		
		// Browse button
		JButton btnBrowse = new JButton("Browse...");
		btnBrowse.addActionListener(this);
		btnBrowse.setBounds(308, 54, 89, 23);
		getContentPane().add(btnBrowse);
		
		// Title label
		JLabel lblTitle = new JLabel("Title:");
		lblTitle.setBounds(70, 391, 78, 14);
		getContentPane().add(lblTitle);
		
		// Creator label
		JLabel lblCreatedBy = new JLabel("Created By:");
		lblCreatedBy.setBounds(70, 410, 78, 14);
		getContentPane().add(lblCreatedBy);
		
		// Length (duration) label
		JLabel lblDuration = new JLabel("Length:");
		lblDuration.setBounds(70, 429, 78, 14);
		getContentPane().add(lblDuration);
		
		// Title field
		spotTitle = new JLabel("-");
		spotTitle.setBounds(158, 391, 212, 14);
		getContentPane().add(spotTitle);
		
		// Creator field
		spotCreator = new JLabel("-");
		spotCreator.setBounds(158, 410, 212, 14);
		getContentPane().add(spotCreator);
		
		// Length field
		spotLength = new JLabel("-");
		spotLength.setBounds(158, 429, 212, 14);
		getContentPane().add(spotLength);
		
		// --- And now for the stuff that messes with those controls, making them need to be put up there...
		
		// Start SDP load controls DISABLED!
		recursiveSetEnabled(sdpGroups, false);
		lblTitle.setEnabled(false);
		lblCreatedBy.setEnabled(false);
		lblDuration.setEnabled(false);
		spotTitle.setEnabled(false);
		spotCreator.setEnabled(false);
		spotLength.setEnabled(false);
		
		// Load Spot From Local Storage radio button
		JRadioButton localStorage = new JRadioButton("Load Spot From Local Storage");
		localStorage.setSelected(true);
		localStorage.setBounds(25, 25, 247, 23);
		localStorage.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				loadFromSDP = false;
				
				// Disable SDP load controls
				recursiveSetEnabled(sdpGroups, false);
				lblTitle.setEnabled(false);
				lblCreatedBy.setEnabled(false);
				lblDuration.setEnabled(false);
				spotTitle.setEnabled(false);
				spotCreator.setEnabled(false);
				spotLength.setEnabled(false);
				
				// Enable local load controls
				localFileName.setEnabled(true);
				btnBrowse.setEnabled(true);
			}
		});
		getContentPane().add(localStorage);
		
		// Load Spot From SDP Site radio button
		JRadioButton SDPSite = new JRadioButton("Load Spot From SDP Site");
		SDPSite.setBounds(25, 99, 247, 23);
		SDPSite.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				loadFromSDP = true;
				
				// Enable SDP load controls
				recursiveSetEnabled(sdpGroups, true);
				lblTitle.setEnabled(true);
				lblCreatedBy.setEnabled(true);
				lblDuration.setEnabled(true);
				spotTitle.setEnabled(true);
				spotCreator.setEnabled(true);
				spotLength.setEnabled(true);
				
				// Disable local load controls
				localFileName.setEnabled(false);
				btnBrowse.setEnabled(false);
				
				// If both lists are empty, refresh automatically when Load From SDP is selected
				if ( adsList.isEmpty() && otherMediaList.isEmpty() ) {
					refreshLists();
				}
			}
		});
		getContentPane().add(SDPSite);
		
		// Button group for the radio buttons
		ButtonGroup loadModes = new ButtonGroup();
		loadModes.add(localStorage);
		loadModes.add(SDPSite);
		
		// Load button
		JButton btnLoad = new JButton("Load");
		btnLoad.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent evt) {
				// Are we loading locally or from the SDP site?
				if ( loadFromSDP ) {
					// Check to make sure the Mooler Caster did their OneJob(TM) and actually selected a spot from the list
					if ( chosenSpot == null ) {
						JOptionPane.showMessageDialog(SDPConsole.getMainWindow(), "You have to select a spot from the list, silly!", "You only had ONE JOB!", JOptionPane.ERROR_MESSAGE);
						return;
					}
					
					// Get the spot loading process out of the Event Dispatch Thread
					new Thread(new Runnable() {
						public void run() {
							// Try to load the selected spot
							try {
								spot.load(chosenSpot.url, chosenSpot.name);
							}
							// Error messages!
							catch (LineUnavailableException e) {
								JOptionPane.showMessageDialog(SDPConsole.getMainWindow(), "The audio line is unavailable for some reason.  Try clubbing your computer.", "Error Loading Spot", JOptionPane.ERROR_MESSAGE);
							}
							catch (FileNotFoundException e) {
								JOptionPane.showMessageDialog(SDPConsole.getMainWindow(), "The selected file was not found on the SDP server.  Remind Weasel of his OneJob\u2122, then try again.\n\nAffected Spot: " + chosenSpot.name + "\nFile: " + chosenSpot.url.toString(), "Error Loading Spot", JOptionPane.ERROR_MESSAGE);
							}
							catch (IOException e) {
								JOptionPane.showMessageDialog(SDPConsole.getMainWindow(), "Could not load the spot: " + e.getMessage() + "\n\nThe file may be corrupt.  Microwave it, then try again.", "Error Loading Spot", JOptionPane.ERROR_MESSAGE);
								e.printStackTrace();
							}
							catch (UnsupportedAudioFileException e) {
								JOptionPane.showMessageDialog(SDPConsole.getMainWindow(), "The file you selected is of an unsupported format", "Error Loading Spot", JOptionPane.ERROR_MESSAGE);
							}
							catch (IllegalArgumentException e) {
								JOptionPane.showMessageDialog(SDPConsole.getMainWindow(), "The output line could not be opened.\n\nCheck that your audio output is working properly, and that the file is of a supported format.", "Error Loading Spot", JOptionPane.ERROR_MESSAGE);
								e.printStackTrace();						
							}
						}
					}).start();
				}
				else {
					// Check to make sure the file box is not empty, and berate the user if it is
					if ( localFileName.getText().isEmpty() ) {
						JOptionPane.showMessageDialog(SDPConsole.getMainWindow(), "You have to select a file first, silly!", "You only had ONE JOB!", JOptionPane.ERROR_MESSAGE);
						return;
					}
					
					// Get the spot loading process out of the Event Dispatch Thread
					new Thread(new Runnable() {
						public void run() {
							// Try to load the local file
							try {
								File file = new File(localFileName.getText());
								if ( ! ( file.exists() && file.isFile() ) ) {
									JOptionPane.showMessageDialog(SDPConsole.getMainWindow(), "The file you selected does not exist (or is not valid).", "Error Loading Spot", JOptionPane.ERROR_MESSAGE);
									return;
								}
								spot.load(file.toURI().toURL(), file.getName());
							}
							// Error messages!
							catch (LineUnavailableException e) {
								JOptionPane.showMessageDialog(SDPConsole.getMainWindow(), "The audio line is unavailable for some reason.  Try clubbing your computer.", "Error Loading Spot", JOptionPane.ERROR_MESSAGE);
							}
							catch (IOException e) {
								JOptionPane.showMessageDialog(SDPConsole.getMainWindow(), "Could not load the spot: " + e.getMessage() + "\n\nThe file may be corrupt.  Microwave it, then try again.", "Error Loading Spot", JOptionPane.ERROR_MESSAGE);
								e.printStackTrace();
							}
							catch (UnsupportedAudioFileException e) {
								JOptionPane.showMessageDialog(SDPConsole.getMainWindow(), "The file you selected is of an unsupported format", "Error Loading Spot", JOptionPane.ERROR_MESSAGE);
							}
							catch (IllegalArgumentException e) {
								JOptionPane.showMessageDialog(SDPConsole.getMainWindow(), "The output line could not be opened.\n\nCheck that your audio output is working properly, and that the file is of a supported format.", "Error Loading Spot", JOptionPane.ERROR_MESSAGE);
								e.printStackTrace();						
							}
						}
					}).start();
				}
				
				// Finally, close the window
				setVisible(false);
			}
		});
		btnLoad.setBackground(Color.ORANGE);
		btnLoad.setBounds(70, 477, 89, 23);
		getContentPane().add(btnLoad);
		
		// Cancel button
		JButton btnCancel = new JButton("Cancel");
		btnCancel.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				// Just close the window without doing anything else
				setVisible(false);
			}
		});
		btnCancel.setBackground(Color.RED);
		btnCancel.setBounds(281, 477, 89, 23);
		getContentPane().add(btnCancel);
	}
	
	// Event handler for the browse button (same hack as was used for the spot button)
	public void actionPerformed(ActionEvent e) {
		// Create a file chooser
		JFileChooser chooser = new JFileChooser();
		
		// Recall the last used directory, if there is one
		if ( SDPConsole.lastSpotPath != null ) {
			chooser.setCurrentDirectory(SDPConsole.lastSpotPath);
		}
		
		// Open the dialog and capture the response code
		int response = chooser.showOpenDialog(this);
		
		// If user clicked Cancel, return without changing anything
		if ( response == JFileChooser.CANCEL_OPTION ) {
			return;
		}
		// Otherwise...
		else {
			// Place the file path selected in the chooser into the path text field
			localFileName.setText(chooser.getSelectedFile().getAbsolutePath());
			
			// Store the directory as last used
			SDPConsole.lastSpotPath = chooser.getCurrentDirectory();
		}
	}
	
	// Load the specified CSV into the specified array of SDPSpotInfo objects
	public void loadCSV(URL csv, ArrayList<SDPSpotInfo> spotList, DefaultListModel<String> guiSpotList) {
		// Clear out the old data
		spotList.clear();
		guiSpotList.clear();
		
		try {
			// Open connection
			HttpURLConnection connection = (HttpURLConnection)csv.openConnection();
			
			// Set User-Agent header
			connection.setRequestProperty("User-Agent", "SDPConsole/" + SDPConsole.PROG_VERSION);
			
			// If response is not 200 OK, pop up an error and return
			if ( connection.getResponseCode() != 200 ) {
				JOptionPane.showMessageDialog(this, "Got a " + connection.getResponseCode() + " " + connection.getResponseMessage() + " error from the SDP server when attempting to download:\n\n" + csv.toString() + "\n\nTell Weasel he only had ONE JOB!", "Error Loading Spot List", JOptionPane.ERROR_MESSAGE);
				return;
			}
			
			// Create a buffered reader, then read and parse the response body line-by-line
			BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
			String line;
			while ( (line = in.readLine()) != null ) {
				try {
					String[] params = line.split(",");
					spotList.add( new SDPSpotInfo(params[0], params[1], params[2], new URL(params[3])) );
					guiSpotList.addElement(params[0]);
				}
				catch (Exception e) {
					JOptionPane.showMessageDialog(this, "Got a bad line while parsing the SDP spot list.  Tell Weasel he only had ONE JOB!\n\nLine: " + line + "\nFile: " + csv.toString(), "Error Loading Spot List", JOptionPane.WARNING_MESSAGE);
				}
			}
			
			// We're done here, close the connection
			in.close();
		}
		// Error messages! (well, the ones associated with the exceptions, at least)
		catch (IOException e) {
			JOptionPane.showMessageDialog(this, "Connecting to the SDP server failed with the following error:\n" + e.getMessage() + "\n\nCheck your Internet connection, throw your modem or router out the window if necessary, and try again.", "Error Loading Spot List", JOptionPane.ERROR_MESSAGE);
		}
	}
	
	// Refresh the SDP spot lists
	public void refreshLists() {
		// If we're not already loading...
		if ( ! sdpLoadInProgress ) {		
			sdpLoadInProgress = true;
			
			// Set SDP CSV URLs
			URL adsURL;
			URL otherMediaURL;
			try {
				adsURL = new URL("http://www.stereodustparticles.com/sdp-ads/ads.csv");
				otherMediaURL = new URL("http://www.stereodustparticles.com/othermedia/othermedia.csv");
			}
			catch ( MalformedURLException e ) {
				JOptionPane.showMessageDialog(this, "IfYouLikeGoodIdeas only had ONE JOB!  One or more CSV URLs is malformed:\n\n" + e.getMessage() + "\n\nThe", "D'oh!", JOptionPane.ERROR_MESSAGE);
				return;
			}
			
			// Update ads list
			loadCSV(adsURL, ads, adsList);
			
			// Update other media list
			loadCSV(otherMediaURL, otherMedia, otherMediaList);
			
			sdpLoadInProgress = false;
		}
	}
	
	// Update the SDP Spot Info fields at the bottom of the window
	public void updateSpotInfoDisplay() {
		if ( chosenSpot != null ) {
			spotTitle.setText(chosenSpot.name);
			spotCreator.setText(chosenSpot.creator);
			spotLength.setText(chosenSpot.duration);
		}
		// If chosenSpot is null, reset the display to dashes
		else {
			spotTitle.setText("-");
			spotCreator.setText("-");
			spotLength.setText("-");
		}
	}
	
	// Recursively enable/disable all controls in a container
	// http://stackoverflow.com/questions/13920279/how-do-i-recursively-disable-my-components-in-swing
	private void recursiveSetEnabled(Component component, boolean enabled) {
	    component.setEnabled(enabled);
	    if (component instanceof Container) {
	        for (Component child : ((Container) component).getComponents()) {
	            recursiveSetEnabled(child, enabled);
	        }
	    }
	}
}
