
import java.awt.*;
import javax.swing.*;
import java.io.*;

import java.net.URL;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.event.HyperlinkEvent;
import javax.swing.text.html.HTMLFrameHyperlinkEvent;
import javax.swing.text.html.HTMLDocument;
import javax.swing.border.Border;
import java.io.IOException;
import java.net.MalformedURLException;

/**
 * The main class for Azure browser. Contains the main method
 * for the application. <code>Azure</code> is a <code>JFrame</code>, bringing
 * together all the various Swing components of the browser's GUI. It works
 * as a listener for all events (because of it's central role
 * <code>Azure</code> has perfect access to every part of the application)
 * and uses {@link PageHistory} to track the user's browsing of the Internet.
 *
 * @author Pekka Ryynänen
 * @see PageBrowser
 * @see AddressBar
 * @see PageHistory
 */
public class Azure extends JFrame 
    implements ActionListener, HyperlinkListener {

    /**
     * This light blue is the prevailing color of the application.
     */
    public static final Color azure = new Color(207, 221, 255);
    /*
     * Very dark blue for sharp lines of the GUI.
     */
    public static final Color darkBlue = new Color(0, 34, 120);

    /**
     * Azure browser's main method that takes no arguments.
     * Creates a new <code>Azure</code> in screen size and shows it.
     *
     * @param args none or more strings, none of which will be used
     */
    public static void main(String[] args) {
	JFrame.setDefaultLookAndFeelDecorated(true);
	Azure browserApp = new Azure();
	browserApp.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	Toolkit theKit = Toolkit.getDefaultToolkit();
	browserApp.setSize(theKit.getScreenSize());
	browserApp.show();
    }

    private URL emptyURL, invalidURL; // default URLs
    private final JFileChooser pageChooser;
    private AddressBar addressBar;
    private PageBrowser webWindow;
    private JLabel linkTarget;        // shows target when hovering over a link
    private PageHistory history;      // keeps track of browsing
    private JButton backButton, forwardButton;

    /**
     * Constructs a new <code>Azure</code> with its empty {@link PageHistory},
     * menubar, toolbar, {@link AddressBar}, {@link PageBrowser} and link
     * target bar. Checks that the default html files "empty.html" and
     * "invalid.html" needed by the browser are in place. Sets itself and
     * anonymous inner classes as listeners to all GUI components.
     */
    public Azure() {
	setTitle("Azure - Browser in testing");
	Container contents = getContentPane();

	determineEmptyURL();
	determineInvalidURL();
	history = new PageHistory();

	pageChooser = new JFileChooser(System.getProperty("user.dir"));
	pageChooser.addChoosableFileFilter(new HTMLFilter());
	JMenuBar azureMenus = createMenus();
	azureMenus.setBackground(Azure.azure);
	setJMenuBar(azureMenus);

	JPanel upperBar = new JPanel(new BorderLayout());
	JPanel toolBar = createToolBar();
	upperBar.add(toolBar, BorderLayout.WEST);
	addressBar = new AddressBar(this);
	upperBar.add(addressBar, BorderLayout.SOUTH);
	contents.add(upperBar, BorderLayout.NORTH);

	webWindow = new PageBrowser(this, addressBar, invalidURL);
	JScrollPane scrollWindow = new JScrollPane(webWindow);
        scrollWindow.setVerticalScrollBarPolicy
	    (JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
	scrollWindow.setBorder
	    (BorderFactory.createMatteBorder(1,1,1,1, Azure.darkBlue));
	contents.add(scrollWindow, BorderLayout.CENTER);

	JPanel lowerBar = new JPanel(new BorderLayout());
	lowerBar.setBackground(Azure.azure);
	linkTarget = new JLabel(" ");
	linkTarget.setToolTipText("Link  destinations are shown here.");
	lowerBar.add(linkTarget, BorderLayout.CENTER);
	contents.add(lowerBar, BorderLayout.SOUTH);
    }

    /**
     * Sets the given <code>URL</code> address into the {@link AddressBar}
     * and makes the {@link PageBrowser} load it into view. This method
     * should only be called directly to show <code>emptyURL</code> or
     * <code>invalidURL</code> because the page doesn't get added into the
     * {@link PageHistory}.
     *
     * @param newURL the <code>URL</code> to show
     */
    private void updatePageBrowser(URL newURL) {
	addressBar.updateURL(newURL.toString());
	webWindow.loadPage(newURL);
    }

    /**
     * Adds the given <code>URL</code> to {@link PageHistory} as a new
     * entry, checks that the buttons for moving within the history are
     * enabled/disabled as they should be, and calls <code>updatePageBrowser
     * </code> method with the <code>URL</code> as parameter.
     *
     * @param newURL the <code>URL</code> to show
     */
    private void updatePageBrowserAndHistory(URL newURL) {
	history.newEntry(newURL);
	if (!backButton.isEnabled()&& !history.atOldestEntry())
	    backButton.setEnabled(true);
	if (forwardButton.isEnabled())
	    forwardButton.setEnabled(false);
	updatePageBrowser(newURL);
    }

    /**
     * When a link has been clicked, loads that link's target into
     * <code>PageBrowser</code> by calling updatePageBrowserAndHistory.
     * If frames are being used, the target <code>URL</code> is opened only
     * in the target frame and it isn't entered into the history or
     * <code>AddressBar</code>. When a link is pointed at, shows it's target
     * in the lower bar.
     */
    public void hyperlinkUpdate(HyperlinkEvent h) {
	if (h.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) {
	    if (h instanceof HTMLFrameHyperlinkEvent) {
		JEditorPane pane = (JEditorPane) h.getSource();
		HTMLFrameHyperlinkEvent  e = (HTMLFrameHyperlinkEvent)h;
		HTMLDocument doc = (HTMLDocument)pane.getDocument();
		doc.processHTMLFrameHyperlinkEvent(e);
	    } else {
		URL newURL = h.getURL();
		updatePageBrowserAndHistory(newURL);
	    }
	}
	else if (h.getEventType().equals(HyperlinkEvent.EventType.ENTERED)) {
	    String target = h.getURL().toString();
	    linkTarget.setText(target);
	}
	else if (h.getEventType().equals(HyperlinkEvent.EventType.EXITED)) {
	    linkTarget.setText(" ");
	}
    }

    /**
     * Takes the string written in the <code>AddressBar</code> (which
     * <code>Azure</code> listens to) and tries to use it as an
     * <code>URL</code>. If it's not a proper <code>URL</code>, adds
     * "http://" to the beginning and tries again. If a proper URL
     * can be formed, calls updatePageBrowserAndHistory with that parameter.
     * If the result is negative, shows invalid.html.
     */
    public void actionPerformed(ActionEvent a) {
	URL newURL = null;
	String tryURL = a.getActionCommand();
	try {
	    newURL = new URL(tryURL);
	} catch(MalformedURLException e) {
	    try {
		newURL = new URL("http://" + tryURL);
	    } catch(MalformedURLException x) {
		updatePageBrowser(invalidURL);
	    }
	}
	if (newURL != null) {
	    updatePageBrowserAndHistory(newURL);	
	}
    }

    /**
     * Convenience method for checking that the empty.html is in place.
     */
    private void determineEmptyURL() {
	try {
	    java.io.File emptyHTML = new java.io.File
		(System.getProperty("user.dir") + "/empty.html");
	    if (emptyHTML.exists())
		emptyURL = emptyHTML.toURL();
	    else
		System.err.println("Can't find empty.html.");
	} catch(MalformedURLException e) {
	    System.err.println("Bad URL for empty.html.");
	}
    }

    /**
     * Convenience method for checking that the invalid.html is in place.
     */
    private void determineInvalidURL() {
	try {
	    java.io.File invalidHTML = new java.io.File
		(System.getProperty("user.dir") + "/invalid.html");
	    if (invalidHTML.exists())
		invalidURL = invalidHTML.toURL();
	    else
		System.err.println("Can't find invalid.html.");
	} catch(MalformedURLException e) {
	    System.err.println("Bad URL for invalid.html.");
	}
    }

    /**
     * Convenience method for creating the browser's menubar, menu(s) and
     * menu items. "Open page" calls updatePageBrowserAndHistory method with
     * the chosen (supposedly HTML) file's URL as parameter. "Save page" still
     * needs some work, currently it only saves the exact code and text of the
     * HTML page. "Close Azure" surprisingly closes the application.
     *
     * @return the <code>JMenuBar</code> that is the menubar
     */
    private JMenuBar createMenus() {
	JMenuBar azureMenus = new JMenuBar();

	JMenu fileMenu = new JMenu("File");
	fileMenu.setBackground(Azure.azure);
	azureMenus.add(fileMenu);
	//JMenu commandMenu = new JMenu("Commands");
	//azureMenus.add(commandMenu);
	
	JMenuItem openingItem = new JMenuItem("Open page");
	openingItem.setBackground(Azure.azure);
	openingItem.addActionListener(new ActionListener() {
		public void actionPerformed(ActionEvent event) {
		    int returnVal = pageChooser.showOpenDialog(Azure.this);
		    if (returnVal == JFileChooser.APPROVE_OPTION) {
			try {
			    URL newURL = pageChooser.getSelectedFile().toURL();
			    updatePageBrowserAndHistory(newURL);
			} catch(MalformedURLException e) {
			    updatePageBrowser(invalidURL);
			}
		    }
		}
	    });
	fileMenu.add(openingItem);

	JMenuItem savingItem = new JMenuItem("Save page");
	savingItem.setBackground(Azure.azure);
	savingItem.addActionListener(new ActionListener() {
		public void actionPerformed(ActionEvent event) {
		    int returnVal = pageChooser.showSaveDialog(Azure.this);
		    if (returnVal == JFileChooser.APPROVE_OPTION) {
			File sFile = pageChooser.getSelectedFile();
			try {
			    if (!sFile.exists())
				sFile.createNewFile();
			    if (sFile.canWrite()) {
				BufferedWriter writer = new BufferedWriter
				    (new FileWriter(sFile));
				String s = webWindow.getText();
				writer.write(s);
				writer.flush();
			    }
			} catch(IOException e) {
			    e.printStackTrace();
			}
		    }
		}
	    });
	fileMenu.add(savingItem);

	JMenuItem closingItem = new JMenuItem("Close Azure");
	closingItem.setBackground(Azure.azure);
	closingItem.addActionListener(new ActionListener() {
		public void actionPerformed(ActionEvent event) {
		    System.exit(0);
		}
	    });
	fileMenu.add(closingItem);

	return azureMenus;
    }

    /**
     * Convenience method for creating the browser's toolbar, it's
     * buttons and their listeners.
     *
     * @return the <code>JPanel</code> that is the toolbar
     */
    private JPanel createToolBar() {
	JPanel toolBar = new JPanel();
	toolBar.setOpaque(false);

	Border line = BorderFactory.createLineBorder(Azure.darkBlue);
	Border title;

	JButton reloadButton = new JButton("  Reload  ");
	reloadButton.setBorder(line);
	reloadButton.setIcon(new ImageIcon("images/toolbarButtonGraphics/" +
					 "general/Refresh24.gif"));
	reloadButton.setVerticalTextPosition(AbstractButton.TOP);
	reloadButton.setHorizontalTextPosition(AbstractButton.CENTER);
	reloadButton.setToolTipText("Reload this page");
	reloadButton.addActionListener(new ActionListener() {
		public void actionPerformed(ActionEvent event) {
		    if (emptyURL == null) {
			System.err.println("Cannot reload.");
		    }
		    else {
			URL newURL = webWindow.getPage();
			if (newURL != null) {
			    updatePageBrowser(emptyURL);
			    updatePageBrowser(newURL);
			}
		    }
		}
	    });
	toolBar.add(reloadButton);

	backButton = new JButton("    Back    ");
	backButton.setBorder(line);
	backButton.setIcon(new ImageIcon("images/toolbarButtonGraphics/" +
					 "navigation/Back24.gif"));
	backButton.setVerticalTextPosition(AbstractButton.TOP);
	backButton.setHorizontalTextPosition(AbstractButton.CENTER);
	backButton.setToolTipText("Back to earlier page");
	backButton.addActionListener(new ActionListener() {
		public void actionPerformed(ActionEvent event) {
		    URL newURL = history.goBack();
		    if (!forwardButton.isEnabled())
			forwardButton.setEnabled(true);
		    if (history.atOldestEntry())
			backButton.setEnabled(false);
		    updatePageBrowser(newURL);
		}
	    });
	backButton.setEnabled(false);
	toolBar.add(backButton);

	forwardButton = new JButton(" Forward ");
	forwardButton.setBorder(line);
	forwardButton.setIcon(new ImageIcon("images/toolbarButtonGraphics/" +
					 "navigation/Forward24.gif"));
	forwardButton.setVerticalTextPosition(AbstractButton.TOP);
	forwardButton.setHorizontalTextPosition(AbstractButton.CENTER);
	forwardButton.setToolTipText("Forward to later page");
	forwardButton.addActionListener(new ActionListener() {
		public void actionPerformed(ActionEvent event) {
		    URL newURL = history.goForward();
		    if (!backButton.isEnabled())
			backButton.setEnabled(true);
		    if (history.atNewestEntry())
			forwardButton.setEnabled(false);
		    updatePageBrowser(newURL);
		}
	    });
	forwardButton.setEnabled(false);
	toolBar.add(forwardButton);

	return toolBar;
    }

    /**
     * A <code>FileFilter</code> used on the <code>JFileChooser</code> when
     * browsing the contents of the computer.
     */
    private class HTMLFilter extends javax.swing.filechooser.FileFilter {
	//Accept all directories and html files.
	public boolean accept(java.io.File f) {
	    if (f.isDirectory())
		return true;
	    String s = f.getName();
	    int i = s.lastIndexOf('.');
	    String extension = new String();
	    if (i > 0 &&  i < s.length() - 1)
		extension = s.substring(i+1).toLowerCase();
	    if (extension != null) {
		if (extension.equals("html"))
                    return true;
            }
	    return false;
        }
	public String getDescription() {
	    return "*.html";
	}
    }

}

