/*
    JPC: A x86 PC Hardware Emulator for a pure Java Virtual Machine
    Release Version 2.0

    A project from the Physics Dept, The University of Oxford

    Copyright (C) 2007 Isis Innovation Limited

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License version 2 as published by
    the Free Software Foundation.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
    Details (including contact information) can be found at: 

    www.physics.ox.ac.uk/jpc
*/

package org.jpc.j2se;

import java.util.*;
import java.io.*;
import java.beans.*;
import java.awt.*;
import java.text.*;
import java.net.*;
import java.awt.color.*;
import java.awt.image.*;
import java.awt.event.*;
import java.awt.Desktop;
import java.awt.Toolkit;

import javax.swing.*;
import javax.swing.event.*;

import org.jpc.emulator.processor.*;
import org.jpc.emulator.*;
import org.jpc.support.*;
import org.jpc.emulator.motherboard.*;
import org.jpc.emulator.memory.*;
import org.jpc.emulator.memory.codeblock.*;
import org.jpc.emulator.peripheral.*;
import org.jpc.emulator.pci.peripheral.*;

  
public class JPCApplication
{
    public static final int WIDTH = 720;
    public static final int HEIGHT = 400;
    public static final int DEPTH = 32;
    private static String[] defaultArgs = { "-fda", "mem:floppy.img", "-hda", "mem:dosgames.img", "-boot", "fda"};

    public static void main(String[] args) throws Exception
    { 
        try
        {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        }
        catch (Exception e) {}

        ApplicationFrame frame;
        if (args.length == 0)
        {
            frame = new ApplicationFrame(defaultArgs);
        }
        else
        {
            frame = new ApplicationFrame(args);
        }
        frame.setBounds(100, 100, WIDTH+20, HEIGHT+70);
        frame.setIconImage(Toolkit.getDefaultToolkit().getImage(ClassLoader.getSystemResource("website/jpc/images/jpcicon.tif")));
        frame.validate();
        frame.setVisible(true);
    }

    public static class ApplicationFrame extends JFrame implements ActionListener, Runnable, PropertyChangeListener
    {
        private boolean running, started = false;
        private PC pc;
        private PCMonitor monitor;
        private JMenuItem load, image, quit,  start, reset, aboutUs, gettingStarted;
        private JCheckBoxMenuItem doubleSize;
        private Thread runner;
        private JEditorPane licence, instructionsText;
        private JScrollPane monitorPane;
        private JFileChooser fc = null;
        private BufferedWriter out;
        private String aboutUsText = "JPC: Developed since August 2005 in Oxford University's Subdepartment of Particle Physics.\n\nFor more information visit our website at:\nhttp://www.physics.ox.ac.uk/jpc";
        private String defaultLicence = "do not copy on pain of death";
    
        ApplicationFrame(String[] args) throws Exception
        {
            super("JPC");
        
            this.pc = PC.createPC(args, new VirtualClock()); 
            monitor = new PCMonitor(pc);
            monitor.startUpdateThread();
            monitor.addPropertyChangeListener(this);

            SwingUtilities.invokeLater(new R1());
        }

        public void propertyChange(PropertyChangeEvent evt) 
        {
            if (monitor.mouseCaptured())
                setTitle("JPC: Double right click to release mouse");
            else
                setTitle("JPC");
        }

        class R1 implements Runnable
        {
            public void run()
            {
                createAndShowGUI();
            }
        }
    
        class RStart implements Runnable
        {
            public void run()
            {
                monitorPane.setViewportView(monitor);
                start.setText("Pause");
            }
        }

        class RStop implements Runnable
        {
            public void run()
            {
                start.setText("Continue");
            }
        }

        private synchronized void stop()
        {
            running = false;
            javax.swing.SwingUtilities.invokeLater(new RStop());
            try
            {
                runner.join(5000);
            }
            catch (Throwable t){}
        
            try
            {
                runner.stop();
            }
            catch (Throwable t) {}
            runner = null;
            monitor.dispose();
        }
    
        private synchronized void start()
        {
            if (running)
                return;
            started = true;
            javax.swing.SwingUtilities.invokeLater(new RStart());
            running = true;
            runner = new Thread(this);
            runner.setPriority(Thread.NORM_PRIORITY-1);
            runner.start();

            monitor.startUpdateThread();
            monitor.validate();
            monitor.requestFocus();
        }

        private void reset()
        {
            stop();
            pc.reset();
            start();
        }
    
        private void createAndShowGUI()
        {
       
            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            JMenuBar bar = new JMenuBar();
            JMenu file = new JMenu("File");
            load = file.add("Select directory");
            load.addActionListener(this);
            image = file.add("Load Drive Image");
            image.addActionListener(this);
            start = file.add("Start");
            start.addActionListener(this);
            reset = file.add("Reset");
            reset.addActionListener(this);
            file.addSeparator();
            doubleSize = new JCheckBoxMenuItem("Double Size");
            file.add(doubleSize);
            doubleSize.addActionListener(this);
            file.addSeparator();
            quit = file.add("Quit");
            quit.addActionListener(this);
            JMenu help = new JMenu("Help");
            gettingStarted = help.add("Getting Started");
            gettingStarted.addActionListener(this);
            aboutUs = help.add("About JPC");
            aboutUs.addActionListener(this);
            bar.add(file);
            bar.add(help);
            bar.add(Box.createHorizontalGlue());
            setJMenuBar(bar);

            try
            {
                
                licence = new JEditorPane(ClassLoader.getSystemResource("org/jpc/j2se/licence.html"));
            }
            catch (Exception e)
            {
                e.printStackTrace();
                try
                {
                    licence = new JEditorPane("text/html", defaultLicence);
                }
                catch (Exception f) {}
            }
            licence.setEditable(false);
            //licence.addHyperlinkListener(this);
            monitorPane = new JScrollPane(licence);
            getContentPane().add("Center", monitorPane);
            getContentPane().validate();
        }

        public void actionPerformed(ActionEvent evt)
        {
            if (evt.getSource() == quit)
                System.exit(0);
            else if (evt.getSource() == start)
            {
                if (running)
                    stop();
                else
                    start();
            }
            else if (evt.getSource() == reset)
                reset();
            else if (evt.getSource() == doubleSize)
                monitor.setDoubleSize(doubleSize.isSelected());
            else if (evt.getSource() == load)
            {
                int load = 0;
                if (started)
                    load = JOptionPane.showOptionDialog(this, "Selecting a directory now will cause JPC to reboot. Are you sure you want to continue?", "Warning", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null, new String[] {"Continue","Cancel"}, "Continue");
                if (load == 0)
                {
                    if (running)
                        stop();
                    if (fc == null)
                        fc = new JFileChooser();
                    fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
                    try
                    {
                        BufferedReader in = new BufferedReader(new FileReader("prefs.txt"));
                        String path = in.readLine();
                        in.close();
                        if (path != null)
                        {
                            File f = new File(path);
                            if (f.isDirectory())
                                fc.setCurrentDirectory(f);
                        }
                    }
                    catch (Exception e) {}

                    int returnVal = fc.showDialog(this, "Open Directory");
                    File file = fc.getSelectedFile();
                    try
                    {
                        if (file != null)
                        {
                            out = new BufferedWriter(new FileWriter("prefs.txt"));
                            out.write(file.getPath());
                            out.close();
                        }
                    }
                    catch (Exception e) {e.printStackTrace();}

                    if (returnVal == 0)
                        try
                        {
                            BlockDevice hda = new TreeBlockDevice(file, true);
                            DriveSet drives = pc.getDrives();
                            drives.setHardDrive(0, hda);
                        }
                        catch (IOException e)
                        {
                            e.printStackTrace();
                            //handle exception
                        
                        }
                        catch (IndexOutOfBoundsException e)
                        {
                            //there were too many files in the directory tree selected
                            System.out.println("too many files");
                            JOptionPane.showMessageDialog(this, "The directory you selected contains too many files. Try selecting a directory with fewer contents.", "Error loading directory", JOptionPane.ERROR_MESSAGE, null);
                            return;
                        }
                    monitor.dispose();
                    pc.reset();
                    monitor.revalidate();
                    monitor.requestFocus();
                    if (started)
                        reset();
                }
            }
            else if (evt.getSource() == image)
            {
                int load = 0;
                if (started)
                    load = JOptionPane.showOptionDialog(this, "Selecting an image now will cause JPC to reboot. Are you sure you want to continue?", "Warning", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null, new String[] {"Continue","Cancel"}, "Continue");
                if (load == 0)
                {
                    if (running)
                        stop();
                    if (fc == null)
                        fc = new JFileChooser();
                    ImageFileFilter filter = new ImageFileFilter();
                    fc.setFileFilter(filter);
                    try
                    {
                        BufferedReader in = new BufferedReader(new FileReader("prefs.txt"));
                        String path = in.readLine();
                        in.close();
                        if (path != null)
                        {
                            File f = new File(path);
                            if (f.isDirectory())
                                fc.setCurrentDirectory(f);
                        }
                    }
                    catch (Exception e) {}

                    int returnVal = fc.showDialog(this, "Load Drive Image");
                    File file = fc.getSelectedFile();
                    try
                    {
                        if (file != null)
                        {
                            out = new BufferedWriter(new FileWriter("prefs.txt"));
                            out.write(file.getPath());
                            out.close();
                        }
                    }
                    catch (Exception e) {e.printStackTrace();}

                    if (returnVal == 0)
                        try
                        {
                            BlockDevice device = null;
                            Class blockClass = Class.forName("org.jpc.support.FileBackedSeekableIODevice");
                            SeekableIODevice ioDevice = (SeekableIODevice)(blockClass.newInstance());
                            ioDevice.configure(file.getPath());
                            device = new RawBlockDevice(ioDevice);
                            DriveSet drives = pc.getDrives();
                            drives.setHardDrive(0, device);
                        }
                        catch (IOException e)
                        {
                            e.printStackTrace();
                            //handle exception
                        
                        }
                        catch (Exception e)
                        {
                            e.printStackTrace();
                        }
                    monitor.dispose();
                    pc.reset();
                    monitor.revalidate();
                    monitor.requestFocus();
                    if (started)
                        reset();
                }
            }
            else if (evt.getSource() == gettingStarted)
            {
                //show basic instructions
                JOptionPane.showMessageDialog(this, "1. Load the directory which has your documents in it via the File menu. \n2. Click on Continue/Start in the File menu.", "Getting Started", JOptionPane.INFORMATION_MESSAGE, null);
            }
            else if (evt.getSource() == aboutUs)
            {
                //show basic instructions
                Object[] buttons = {"Visit our Website", "Ok"};
                int i =JOptionPane.showOptionDialog(this, aboutUsText, "JPC info", JOptionPane.YES_NO_OPTION, JOptionPane.INFORMATION_MESSAGE, null, buttons, buttons[1]);
                if (i == 0)
                {
                    Desktop desktop = null;
                    if (Desktop.isDesktopSupported()) 
                    {
                        desktop = Desktop.getDesktop();
                        try
                        {
                            desktop.browse(new URI("http://www.physics.ox.ac.uk/jpc/"));
                        }
                        catch (Exception e)
                        {//display failure
                        }
                    }
                }
            }
        }
    
        public void run()
        {
            pc.getSystemClock().resume();
            long execCount = 0;
            try
            {
                while (running)
                {
                    try
                    {
                        while (running)
                        {
                            execCount += pc.executeRealMode();
                        }
                    }
                    catch (ModeSwitchException e) {}
                
                    try
                    {
                        while (running)
                        {                        
                            execCount += pc.executeProtectedMode();
                        }
                    }
                    catch (ModeSwitchException e) {}
                }
            }
            catch (Exception e)
            {
                System.err.println("Caught exception @ Address:0x" + Integer.toHexString(pc.getProcessor().getInstructionPointer()));
                System.err.println(e);
                e.printStackTrace();
            }
            finally
            {
                pc.getSystemClock().pause();
                System.err.println("PC Paused");
            }
        }

        private class ImageFileFilter extends javax.swing.filechooser.FileFilter
        {
            public boolean accept(File f)
            {
                if (f.isDirectory())
                    return true;
                String name = f.getName();
                int pos = name.lastIndexOf('.');
                String ext = name.substring(pos+1);
                if (ext.compareToIgnoreCase("img") == 0)
                    return true;
                return false;
            }

            public String getDescription()
            {
                return "Shows disk image files and directories";
            }
        }

    }
}
