package com.stereodustparticles.mooler_caster_console;

import java.awt.Color;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Properties;

import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.JOptionPane;

// 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 contains the code for handling saved soundboard layouts

public class BoardLayout {
	// Write the specified board to the specified file
	// Adapted from https://www.mkyong.com/java/java-properties-file-examples/
	public static void writeToFile(File file, Spot[] board) {
		Properties layout = new Properties();
		FileOutputStream out = null;
		
		// Set a key for the version of the program that generated the file
		// This will be useful if the format ever changes
		layout.setProperty("SDP_VERSION", Double.toString(SDPConsole.PROG_VERSION));
		
		try {
			// Open an output stream
			out = new FileOutputStream(file);
			
			// Generate the properties
			for ( int i = 0; i < board.length; i++ ) {
				if ( board[i].isLoaded() ) {
					layout.setProperty("spot" + i, spotToString(board[i]));
				}
			}
			
			// Write the file out to disk
			layout.store(out, "SDP Mooler Caster Console - Saved Soundboard Layout");
			
			// Update current file fields
			SDPConsole.currentLayout = file;
			SDPConsole.fileUpToDate = true;
		}
		catch (IOException e) {
			JOptionPane.showMessageDialog(SDPConsole.getMainWindow(), "Could not save the file: " + e.getMessage() + "\n\nCheck that the file name you entered is valid and that the folder you selected is writable.\nIf the problem persists, bring your computer to the SDP service counter for a clubbing.", "Error Saving Layout", JOptionPane.ERROR_MESSAGE);
		}
		finally {
			if ( out != null ) {
				try {
					out.close();
				}
				catch ( IOException e ) {
					// Something is very wrong
					e.printStackTrace();
				}
			}
		}
	}
	
	// Load the spots from the specified file into the specified board
	// Again, adapted from https://www.mkyong.com/java/java-properties-file-examples/
	public static void loadFromFile(File file, Spot[] board) {
		// Prepare a BoardLoadStatus dialog
		BoardLoadStatus status = new BoardLoadStatus(file.getName());
		status.setProgressMax(board.length);
		status.setVisible(true);
		
		// Prepare a properties table and input stream
		Properties layout = new Properties();
		FileInputStream input = null;
		
		try {
			// Open the file
			input = new FileInputStream(file);
			
			// Load the file into the properties table
			layout.load(input);
			
			// Check file version
			String fileVerStr = layout.getProperty("SDP_VERSION");
			if ( fileVerStr == null ) {
				throw new Exception("No version header was found in the specified file.  Are you sure it's really a layout file?");
			}
			double fileVersion = Double.parseDouble(fileVerStr);
			if ( fileVersion > SDPConsole.PROG_VERSION ) {
				// File is newer than the Console - Don't try to load it; schema could have changed
				throw new Exception("This layout file was generated by a newer version of the Mooler Caster Console.\n\nYou will need to upgrade your copy of the program in order to load it.");
			}
			else if ( fileVersion < SDPConsole.PROG_VERSION ) {
				// File is older than the Console - In this version, that's OK, but re-saving the layout will change the file's version header
				JOptionPane.showMessageDialog(status, "This layout file was generated by an older version of the Mooler Caster Console.\n\nIt can be loaded, but if you make any changes, it will no longer load in the earlier version.", "Older Layout Warning", JOptionPane.WARNING_MESSAGE);
			}
			
			// Loop across the board and load each spot, if present in the table.  If not present, clear it.
			for ( int i = 0; i < board.length; i++ ) {
				String spotConfig = layout.getProperty("spot" + i);
				if ( spotConfig != null ) {
					try {
						loadSpot(spotConfig, board[i], status);
					}
					// Error messages!
					catch (MalformedURLException e) {
						JOptionPane.showMessageDialog(status, "This layout contains a malformed URL.  Were you messing with it?", "Error Loading Spot", JOptionPane.ERROR_MESSAGE);
					}
					catch (LineUnavailableException e) {
						JOptionPane.showMessageDialog(status, "The audio line is unavailable for some reason.  Try clubbing your computer.", "Error Loading Spot", JOptionPane.ERROR_MESSAGE);
					}
					catch (FileNotFoundException e) {
						JOptionPane.showMessageDialog(status, "One of the files specified in this layout is missing.  You will need to reload it from its new location and resave the layout.", "Error Loading Spot", JOptionPane.ERROR_MESSAGE);
					}
					catch (IOException e) {
						JOptionPane.showMessageDialog(status, "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(status, "The file you selected is of an unsupported format", "Error Loading Spot", JOptionPane.ERROR_MESSAGE);
					}
					catch (IllegalArgumentException e) {
						JOptionPane.showMessageDialog(status, "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();						
					}
				}
				else {
					board[i].clear();
				}
				
				// Update dialog's progress bar (+1 because i is zero-based)
				status.setCurrentProgress(i + 1);
			}
			
			// Update current file fields
			SDPConsole.currentLayout = file;
			SDPConsole.fileUpToDate = true;
		}
		catch (IOException e) {
			JOptionPane.showMessageDialog(SDPConsole.getMainWindow(), "Could not load the layout: " + e.getMessage() + "\n\nVerify that the path is correct, club your computer if necessary, then try again.", "Error Loading Layout", JOptionPane.ERROR_MESSAGE);
			e.printStackTrace();
		}
		catch (Exception e) {
			// If it's not an IOException, it goes here
			// It was presumably thrown by one of our file checks, so we'll just show the message as-is
			JOptionPane.showMessageDialog(SDPConsole.getMainWindow(), e.getMessage(), "Error Loading Layout", JOptionPane.ERROR_MESSAGE);
		}
		finally {
			if (input != null) {
				try {
					input.close();
				}
				catch (IOException e) {
					// Something really bad has happened
					e.printStackTrace();
				}
			}
			
			// Close the dialog
			status.setVisible(false);
		}
	}
	
	// Create a string containing the state of the given Spot
	private static String spotToString(Spot s) {
		// Build a string of the following format
		// Color set: Name,URL,R,G,B
		// No color set: Name,URL,NO_COLOR
		StringBuilder sb = new StringBuilder();
		sb.append(s.getName());
		sb.append(",");
		sb.append(s.getURL());
		sb.append(",");
		if ( s.getColor() != null ) {
			sb.append(s.getColor().getRed());
			sb.append(",");
			sb.append(s.getColor().getGreen());
			sb.append(",");
			sb.append(s.getColor().getBlue());
		}
		else {
			sb.append("NO_COLOR");
		}
		return sb.toString();
	}
	
	// Load the specified spot with the data from the specified string (updating the specified dialog)
	private static void loadSpot(String data, Spot s, BoardLoadStatus status) throws IllegalArgumentException, MalformedURLException, LineUnavailableException, IOException, UnsupportedAudioFileException {
		// Split the data from the properties table back into its components
		String[] usefulData = data.split(",");
		
		// Update the dialog with the correct spot name
		status.setCurrentSpot(usefulData[0]);
		
		// Check if color was set or not, and act accordingly
		if ( usefulData[2].equals("NO_COLOR")) {
			// Load the Spot
			s.load(new URL(usefulData[1]), usefulData[0]);
		}
		else {
			// Rebuild the Color object for the Spot
			Color color = new Color(Integer.parseInt(usefulData[2]), Integer.parseInt(usefulData[3]), Integer.parseInt(usefulData[4]));
			
			// Load the Spot
			s.load(new URL(usefulData[1]), usefulData[0], color);
		}
	}
}
