/*
 * Main.java
 *
 * Status: Functional
 */

package gui;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

import java.io.*;
import java.util.*;

import finance.*;
import gui.dialog.*;
import utility.*;

/**
 * The main application window; also contains the application entry point.  This
 * is a Singleton class.
 */
public final class Main extends JFrame
 {
  /**
   * This class is used to invoke {@code setVisible} asynconously from the event
   * dispatching thread.
   */
  private static class Visiblizer implements Runnable
   {
    /** Do-nothing constructor.  To prevent creation of {@code Main$1.class}. */
    Visiblizer() { }
    /** Makes the main window visible. */
    public void run() { instance.setVisible(true); }
   }


  /**
   * This class contains the methods for handling the menu items.  The methods
   * are in a separate class partly for encapsulation (as an implementation
   * detail requires them to be {@code public}), and partly to keep them
   * organized.
   */
  private class MenuHandler
   {
    /**
     * Do-nothing constructor.  To prevent creation of {@code Main$1.class}.
     */
    MenuHandler() { }


    /**
     * Handles the File->New menu item.
     */
    public void menuFile_New_Handler(ActionEvent event)
     {
      //if(fileChooser.showSaveDialog(Main.this) != JFileChooser.APPROVE_OPTION)
      //  return;
      fileChooser.setDialogTitle("Create");
      if(fileChooser.showDialog(Main.this, "Create") != JFileChooser.APPROVE_OPTION)
        return;

      File file = fileChooser.getSelectedFile();
      if(file.exists())
       {
        errorMessage("File already exists.", "File Error");
        return;
       }

      //TODO: only apply this modification if .fdb filter is selected
      if(!file.getPath().endsWith(".fdb"))
        file = new File(file.getPath() + ".fdb");
      if(file.exists())
       {
        errorMessage("File already exists.", "File Error");
        return;
       }

      if(FinancialDatabase.createNew(file) != Status.OK)
        return;

      openDatabase(file);
     }


    /**
     * Handles the File->Open menu item.
     */
    public void menuFile_Open_Handler(ActionEvent event)
     {
      if(fileChooser.showOpenDialog(Main.this) != JFileChooser.APPROVE_OPTION)
        return;

      File file = fileChooser.getSelectedFile();

      openDatabase(file);
     }


    /**
     * Handles the File->Save As menu item.
     */
    public void menuFile_SaveAs_Handler(ActionEvent event)
     {
      if(fileChooser.showSaveDialog(Main.this) != JFileChooser.APPROVE_OPTION)
        return;

      File file = fileChooser.getSelectedFile();
      if(file.exists())
       {
        errorMessage("File already exists.", "File Error");
        return;
       }

      //TODO: only apply this modification if .fdb filter is selected
      if(!file.getPath().endsWith(".fdb"))
        file = new File(file.getPath() + ".fdb");
      if(file.exists())
       {
        errorMessage("File already exists.", "File Error");
        return;
       }

      if(DB.saveFileAs(file) != Status.OK)
       {
        errorMessage("Error saving file.", "File Error");
        return;
       }

      try
       { properties.setProperty("file.last", file.getCanonicalPath()); }
      catch(Exception ignored)
       { }
     }


    /**
     * Handles the File->Exit menu item.
     */
    public void menuFile_Exit_Handler(ActionEvent event)
     {
      setVisible(false);
      dispose();
     }


    public void menuView_Ledger_Handler(ActionEvent event)
     {
      displayLedger(DB.getLedger());
     }


    public void menuView_Journal_Handler(ActionEvent event)
     {
      displayJournal(DB.getJournal());
     }


    /**
     * Handles the Help->Index menu item.
     */
    public void menuHelp_Index_Handler(ActionEvent event)
     {
      HelpDialog.showIndex();
     }


    /**
     * Handles the Help->Contents menu item.
     */
    public void menuHelp_Contents_Handler(ActionEvent event)
     {
      HelpDialog.showContents();
     }


    /**
     * Handles the Help->About menu item.
     */
    public void menuHelp_About_Handler(ActionEvent event)
     {
      AboutDialog.showDialog();
     }
   }


  /** The only instance of this class. */
  public static final Main instance;


  static
   {
    JFrame.setDefaultLookAndFeelDecorated(true);
    instance = new Main();
   }


  /**
   * The application entry point.
   */
  public static void main(String[] args)
   {
    String lastFile = instance.properties.getProperty("file.last");
    if(lastFile != null)
     {
      File file = new File(lastFile);
      if(file.exists())
        instance.openDatabase(file);
     }

    SwingUtilities.invokeLater(new Visiblizer());
   }


  /**
   * Gets the currently open {@code FinancialDatabase}.
   */
  public static FinancialDatabase getDB()
   { return instance.DB; }


  private final File propertiesFile = new File(propertiesFilePath());
  private final Properties properties = new Properties();
  private final MenuHandler menuHandler = new MenuHandler();
  private final JFileChooser fileChooser = new JFileChooser();

  /** The panel at the top-left of the main window. */
  private JPanel panelTopLeft = new JPanel(new FlowLayout(FlowLayout.LEFT));
  /** The panel at the top-right of the main window. */
  private JPanel panelTopRight = new JPanel(new FlowLayout(FlowLayout.RIGHT));
  /** The main panel, in the center of the main window. */
  private JPanel panelCenter = new JPanel(new GridLayout(1, 1));
  private JLabel labelMain = new JLabel("", SwingConstants.LEFT);

  private Map<Object, JComponent> componentMap = new Hashtable<Object, JComponent>();

  private FinancialDatabase DB = null;


  /**
   * Constructs the main application window.
   */
  private Main()
   {
    super("Double Entry Accounting Program - Personal Edition");
    setDefaultCloseOperation(DISPOSE_ON_CLOSE);
    setSize(500, 400);
    setIconImage((new ImageIcon(Main.class.getResource("icon.png"))).getImage());

    constructMenu();
    constructFrame();
    constructFileChooser();

    loadProperties();
   }


  /**
   * Called by the constructor to set up the menu bar.
   */
  private void constructMenu()
   {
    JMenuBar menuBar = new JMenuBar();
    setJMenuBar(menuBar);

    //File menu

    JMenu menuFile = new JMenu("File");
    menuFile.setMnemonic(KeyEvent.VK_F);
    menuBar.add(menuFile);

    JMenuItem menuFileNew = new JMenuItem("New");
    menuFile.setMnemonic(KeyEvent.VK_N);
    menuFileNew.addActionListener(new ActionHandler(menuHandler, "menuFile_New_Handler"));
    menuFile.add(menuFileNew);

    JMenuItem menuFileOpen = new JMenuItem("Open");
    menuFile.setMnemonic(KeyEvent.VK_O);
    menuFileOpen.addActionListener(new ActionHandler(menuHandler, "menuFile_Open_Handler"));
    menuFile.add(menuFileOpen);

    JMenuItem menuFileSaveAs = new JMenuItem("Save As");
    menuFile.setMnemonic(KeyEvent.VK_A);
    menuFileSaveAs.addActionListener(new ActionHandler(menuHandler, "menuFile_SaveAs_Handler"));
    menuFile.add(menuFileSaveAs);

    menuFile.addSeparator();

    JMenuItem menuFileExit = new JMenuItem("Exit");
    menuFile.setMnemonic(KeyEvent.VK_X);
    menuFileExit.addActionListener(new ActionHandler(menuHandler, "menuFile_Exit_Handler"));
    menuFile.add(menuFileExit);

    //View menu

    JMenu menuView = new JMenu("View");
    menuFile.setMnemonic(KeyEvent.VK_V);
    menuBar.add(menuView);

    JMenuItem menuViewLedger = new JMenuItem("General Ledger");
    menuFile.setMnemonic(KeyEvent.VK_L);
    menuViewLedger.addActionListener(new ActionHandler(menuHandler, "menuView_Ledger_Handler"));
    menuView.add(menuViewLedger);

    JMenuItem menuViewJournal = new JMenuItem("General Journal");
    menuFile.setMnemonic(KeyEvent.VK_J);
    menuViewJournal.addActionListener(new ActionHandler(menuHandler, "menuView_Journal_Handler"));
    menuView.add(menuViewJournal);

    //Help menu

    JMenu menuHelp = new JMenu("Help");
    menuHelp.setMnemonic(KeyEvent.VK_H);
    menuBar.add(menuHelp);

    JMenuItem menuHelpIndex = new JMenuItem("Index");
    menuFile.setMnemonic(KeyEvent.VK_I);
    menuHelpIndex.addActionListener(new ActionHandler(menuHandler, "menuHelp_Index_Handler"));
    menuHelp.add(menuHelpIndex);

    JMenuItem menuHelpContents = new JMenuItem("Contents");
    menuFile.setMnemonic(KeyEvent.VK_C);
    menuHelpContents.addActionListener(new ActionHandler(menuHandler, "menuHelp_Contents_Handler"));
    menuHelp.add(menuHelpContents);

    menuHelp.addSeparator();

    JMenuItem menuHelpAbout = new JMenuItem("About");
    menuFile.setMnemonic(KeyEvent.VK_A);
    menuHelpAbout.addActionListener(new ActionHandler(menuHandler, "menuHelp_About_Handler"));
    menuHelp.add(menuHelpAbout);
   }


  /**
   * Called by the constructor to set up the application frame.
   */
  private void constructFrame()
   {
    setLayout(new GridBagLayout());
    GridBagConstraints c = new GridBagConstraints();

    c.gridwidth = GridBagConstraints.RELATIVE;
    c.weightx = 1;
    c.fill = GridBagConstraints.BOTH;
    c.anchor = GridBagConstraints.NORTHWEST;
    //c.insets = new Insets(0, 1, 0, 1);
    add(panelTopLeft, c);

    c.gridwidth = GridBagConstraints.REMAINDER;
    c.weightx = 1;
    c.anchor = GridBagConstraints.NORTHEAST;
    add(panelTopRight, c);

    c.gridwidth = 2;
    c.gridheight = GridBagConstraints.REMAINDER;
    c.weightx = 1;
    c.weighty = 1;
    c.fill = GridBagConstraints.BOTH;
    c.anchor = GridBagConstraints.CENTER;
    add(panelCenter, c);

    labelMain.setFont(labelMain.getFont().deriveFont(labelMain.getFont().getSize2D() + 2));
    panelTopLeft.add(labelMain);
   }


  /**
   * Called by the constructor to set up the file chooser.
   */
  private void constructFileChooser()
   {
    fileChooser.addChoosableFileFilter(new BasicFileFilter("Financial Database Files", ".fdb"));
   }


  /**
   * Opens a financial database.
   *
   * @param file the file containing the financial database to open.
   */
  private void openDatabase(File file)
   {
    setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
    DB = new FinancialDatabase(file);
    DB.loadFile();

    try
     { properties.setProperty("file.last", file.getCanonicalPath()); }
    catch(Exception ignored)
     { }

    String accountTypes = properties.getProperty("account_types");
    if(accountTypes != null)
      DB.parseAccountTypes(accountTypes);

    componentMap.clear();
    displayLedger(DB.getLedger());
    setCursor(Cursor.getDefaultCursor());
   }


  /**
   * Displays the specified {@code Ledger} in the main application window.
   *
   * @param ledger the {@code Ledger} to display.
   */
  public void displayLedger(Ledger ledger)
   {
    if(ledger == null)
      return;

    JComponent comp = componentMap.get(ledger);
    if(comp == null)
     {
      comp = new LedgerComponent(ledger);
      componentMap.put(ledger, comp);
     }

    displayNothing();
    panelCenter.add(comp);
    labelMain.setText("Ledger:  General Ledger");
    repaint();
   }


  /**
   * Displays the specified {@code Journal} in the main application window.
   *
   * @param journal the {@code Journal} to display.
   */
  public void displayJournal(Journal journal)
   {
    if(journal == null)
      return;

    JComponent comp = componentMap.get(journal);
    if(comp == null)
     {
      comp = new JournalComponent(journal);
      componentMap.put(journal, comp);
     }

    displayNothing();
    panelCenter.add(comp);
    labelMain.setText("Journal:  " + journal.getName());
    repaint();
   }


  /**
   * Displays the specified {@code Account} in the main application window.
   *
   * @param account the {@code Account} to display.
   */
  public void displayAccount(Account account)
   {
    if(account == null)
      return;

    JComponent comp = componentMap.get(account);
    if(comp == null)
     {
      comp = new AccountComponent(account);
      componentMap.put(account, comp);
     }

    displayNothing();
    panelCenter.add(comp);
    labelMain.setText("Account:  " + account.getName());
    repaint();
   }


  /**
   * Clears all displays from the main application window.
   */
  private void displayNothing()
   {
    panelCenter.removeAll();
    labelMain.setText("");
   }


  /**
   * Displays an error message to the user.
   *
   * @param message the error message to display.
   * @param title the title for the dialog.
   */
  private void errorMessage(String message, String title)
   {
    if(title == null)
      title = "Error";
    JOptionPane.showMessageDialog(this, message, title, JOptionPane.ERROR_MESSAGE);
   }


  private void loadProperties()
   {
    InputStream in = null;
    try
     {
      in = new BufferedInputStream(new FileInputStream(propertiesFile));
      properties.load(in);
     }
    catch(Exception ignored)
     { }
    try
     { in.close(); }
    catch(Exception ignored)
     { }
   }


  private void saveProperties()
   {
    OutputStream out = null;
    try
     {
      out = new BufferedOutputStream(new FileOutputStream(propertiesFile));
      properties.store(out, null);
     }
    catch(Exception ignored)
     { System.out.println(ignored); }
    try
     { out.close(); }
    catch(Exception ignored)
     { }
   }


  private String propertiesFilePath()
   {
    File file = new File(System.getProperty("user.home"), ".deap");
    if(!file.exists())
      file.mkdir();
    file = new File(file, "properties.txt");
    try
     { return file.getCanonicalPath(); }
    catch(Exception e)
     { return System.getProperty("user.home")+File.separator+".deap"+File.separator+"properties"; }
   }


  public void dispose()
   {
    super.dispose();

    saveProperties();
   }
 }
