/*
 * Copyright (c) 2009, Takeyuki Nagao
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the
 * following conditions are met:
 * 
 *  * Redistributions of source code must retain the above
 *    copyright notice, this list of conditions and the
 *    following disclaimer.
 *  * Redistributions in binary form must reproduce the above
 *    copyright notice, this list of conditions and the
 *    following disclaimer in the documentation and/or other
 *    materials provided with the distribution.
 *    
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 */

package dvi.browser;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import dvi.DviException;
import dvi.api.DviContext;
import dvi.api.DviContextSupport;
import dvi.event.TEvent;
import dvi.event.TEventListener;
import dvi.util.DviDesktop;

// TODO: change api to use URI instead of URL.
// TODO: file filter
// TODO: colored background
// TODO: disable right drag.
// TODO: make larger the resize handle.
// TODO: support large display
// TODO: focus on BrowserPage
// TODO: BrowserPage has a history.
// TODO: report progress while loading fonts.
// TODO: delayed rendering of a page
// TODO: papersize selection
// TODO: auto clipping configurations
// TODO: Make it possible to close a tab.  Awareness of the errors.

public class DviBrowserMain extends JFrame implements ActionListener,
    TEventListener, DviContextSupport
{
  private static final Logger LOGGER = Logger.getLogger(DviBrowserMain.class.getName());
  private static final long serialVersionUID = -970130861572045308L;
  
  private JList listView;
  private DviAddressBar addressBar;
  private DviFileListModel model;
  private DviBrowserStatusBar statusBar;
  private JTabbedPane tabs = new JTabbedPane();
  private DviURLHistory history;
  
  private final DviContextSupport dcs;

  public DviContext getDviContext()
  {
    return dcs.getDviContext();
  }

  public DviBrowserMain(dvi.api.DviContextSupport dcs) {
    super("dvibrowser");
    this.dcs = dcs;
    initializeCompontents();
    open("file:///");
    setDropTarget(new DviBrowserDropTarget(this));
  }
  
  private void initializeCompontents()
  {
    initializeMenuBar();
    
    history = new DviURLHistory(this);
    model = new DviFileListModel();
    listView = new JList(model);
    listView.setCellRenderer(new DviDirectoryIndexCellRenderer(this));
    
    JPanel leftPanel = new JPanel();
    
    leftPanel.setLayout(new BorderLayout());
    leftPanel.add(new JScrollPane(listView));
    
    tabs = new JTabbedPane();
    
    JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
    split.setDividerLocation(192);
    split.setLeftComponent(leftPanel);
    split.setRightComponent(tabs);
    
    addressBar = new DviAddressBar(this);
    statusBar = new DviBrowserStatusBar(this);
    
    JPanel panel = new JPanel();
    panel.setLayout(new BorderLayout());
    panel.add(addressBar, BorderLayout.NORTH);
    panel.add(split, BorderLayout.CENTER);
    panel.add(statusBar, BorderLayout.SOUTH);
    getContentPane().add(panel);
    
    addressBar.getAddressEdit().addKeyListener(new KeyAdapter() {
      public void keyPressed(KeyEvent e)
      {
        if (e.getKeyCode() == KeyEvent.VK_ENTER) {
          openURLOfAddressBar();
        }
      }
    });
    listView.addMouseListener(new MouseAdapter() {
      public void mouseClicked(MouseEvent e)
      {
        if (e.getClickCount() > 1) {
          openBySelection();
        }
      }
    });
    listView.addKeyListener(new KeyAdapter() {
      public void keyPressed(KeyEvent e)
      {
        int code = e.getKeyCode();
        if (code == KeyEvent.VK_ENTER) {
          openBySelection();
        } else if (code == KeyEvent.VK_RIGHT) {
          openBySelectionIfFolder();
        } else if (code == KeyEvent.VK_BACK_SPACE) {
          goBack();
        } else if (code == KeyEvent.VK_LEFT) {
          goUp();
        } else {
          char c = e.getKeyChar();
          String prefix = "" + c;
          int len = model.getSize();
          int pos = listView.getSelectedIndex();
          
          File candidate = null;
          for (int i=0; i<len; i++) {
            pos = (pos + 1) % len;
            File file = (File) model.getElementAt(pos);
            if (file.getName().toLowerCase().startsWith(prefix)) {
              candidate = file;
              break;
            }
          }
          if (candidate != null) {
            listView.setSelectedValue(candidate, true);
          }
        }
      }
    });
    tabs.addChangeListener(new ChangeListener() {
      public void stateChanged(ChangeEvent e)
      {
        updateTabInfo();
      }
    });
  }
  
  private void initializeMenuBar()
  {
    JMenuBar menuBar = new JMenuBar();

    JMenu fileMenu = new JMenu("File");
    fileMenu.setMnemonic(KeyEvent.VK_F);
    menuBar.add(fileMenu);
    setJMenuBar(menuBar);

    JMenuItem fileOpen = new JMenuItem("Open");
    JMenuItem fileReload = new JMenuItem("Reload");
    JMenuItem fileExit = new JMenuItem("Exit");

    fileOpen.setMnemonic(KeyEvent.VK_O);
    fileOpen.setActionCommand("Open");
    fileOpen.addActionListener(this);
    fileOpen.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O,
        ActionEvent.CTRL_MASK));
    fileMenu.add(fileOpen);

    fileReload.setMnemonic(KeyEvent.VK_R);
    fileReload.setActionCommand("Reload");
    fileReload.addActionListener(this);
    fileReload.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_L,
        ActionEvent.CTRL_MASK));
    fileMenu.add(fileReload);

    fileMenu.addSeparator();

    fileExit.setMnemonic(KeyEvent.VK_E);
    fileExit.addActionListener(this);
    fileExit.setActionCommand("Exit");

    fileMenu.add(fileExit);
    fileMenu.addSeparator();
  }

  public void goBack()
  {
    DviURLHistoryRecord rec = history.goBack();
    if (rec != null) {
      open(rec.getURL());
    }
  }

  public void goUp()
  {
    DviURLHistoryRecord rec = history.getCurrentRecord();
    if (rec != null) {
      URL url = getParentURL(rec.getURL());
      open(url);
    }
  }
  
  private URL getParentURL(URL url)
  {
    if (url == null) return null;
    if ("file".equals(url.getProtocol())) {
      try {
        File f = new File(url.getPath());
        if (!f.isDirectory()) {
          f = f.getParentFile();
        }
        return f.getParentFile().toURL();
      } catch (MalformedURLException e) {
        e.printStackTrace();
      }
    }
    return null;
  }

  public void openBySelection()
  {
    File f = (File) listView.getSelectedValue();
    if (f != null) {
      try {
        open(f.toURL());
      } catch (MalformedURLException e1) {
        e1.printStackTrace();
      }
    }
  }

  public void openBySelectionIfFolder()
  {
    File f = (File) listView.getSelectedValue();
    if (f != null && f.isDirectory()) {
      try {
        open(f.toURL());
      } catch (MalformedURLException e1) {
        e1.printStackTrace();
      }
    }
  }

  
  public void open(String [] args) {
    for (String url : args) {
      open(url);
    }
  }

  public void open(URL [] urls) {
    for (URL url : urls)
      open(url);
  }

  public void open(String s)
  {
    try {
      open(toURL(s));
    } catch (DviException e) {
      e.printStackTrace();
    }
  }
  
  public void open(File f)
  throws DviException
  {
    try {
      open(f.toURL());
    } catch (MalformedURLException e) {
      throw new DviException(e);
    }
  }

  protected URL toURL(String s)
  throws DviException
  {
    try {
      URL url = new URL(s);
      return url;
    } catch (MalformedURLException e) {
    }
    try {
      File file = new File(s);
      return file.toURL();
    } catch (MalformedURLException e) {
      throw new DviException(e);
    }
  }
  
  public void open(URL url)
  {
    boolean success = false;
    if ("file".equals(url.getProtocol())) {
      File file = new File(url.getPath());
      if (file.isDirectory()) {
        openDirectoryIndex(file);
        listView.requestFocusInWindow();
        listView.setSelectedIndex(0);
        success  = true;
      } else {
        if (isDviFile(file)) {
          openLocalFile(file);
          success  = true;
        } else if (DviDesktop.isDesktopSupported()) {
          try {
            DviDesktop.getDesktop().open(file);
            success = true;
          } catch (IOException e) {
            LOGGER.warning(e.toString());
            // TODO: Show some message to user.
          }
        } else {
          // Unsupported file type.
          LOGGER.fine("File type not supported: " + file);
          // TODO: Show some message to user.
        }
      }
    }
    if (success) {
      addressBar.getAddressEdit().setText(url.toExternalForm());
      recordURL(url);
    }
  }
  
  private void openDirectoryIndex(File dir)
  {
    model.setDirectory(dir);
  }
  
  private static final Pattern dviFilenamePat = Pattern.compile
    ("^.*(\\.dvi)$", Pattern.CASE_INSENSITIVE);
  
  // TODO: support .gz and .zip file.
  // TODO: move this method to another class.
  private boolean isDviFile(File file)
  {
    if (file == null) return false;
    Matcher mat = dviFilenamePat.matcher(file.getPath());
    if (mat.matches()) {
      String ext = mat.group(1);
      LOGGER.fine("filename extension=" + ext);
      return ".dvi".equals(ext.toLowerCase());
    }
    
    return false;
  }
  
  private void openLocalFile(final File file)
  {
    try {
      File dir = file.getParentFile();
      openDirectoryIndex(dir);
      final DviBrowserPage bp = new DviBrowserPage(this);
      final DviLoadIndicator loadIndicator = bp.getLoadIndicator();
      bp.open(file.toURL());
      loadIndicator.addChangeListener(new ChangeListener() {
        public void stateChanged(ChangeEvent e)
        {
          LOGGER.finer("EDT=" + SwingUtilities.isEventDispatchThread());
          SwingUtilities.invokeLater(new Runnable() {
            public void run() {
              tabs.repaint();
            }
          });
        }
      });
//      tabs.addTab(file.getName(), null, bp, file.getAbsolutePath());
      tabs.addTab(file.getName(), loadIndicator, bp, file.getAbsolutePath());
      tabs.setSelectedComponent(bp);
      bp.getCloseButton().addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e)
        {
          LOGGER.fine("Closing tab: " + bp);
          tabs.remove(bp);
        }
      });
      selectFileInListView(file);
    } catch (MalformedURLException e) {
      LOGGER.warning(e.toString());
    } catch (DviException e) {
      LOGGER.warning(e.toString());
    }
  }
  
  private void selectFileInListView(File file)
  {
    if (file == null) return;
    
    int len = model.getSize();
    for (int i=0; i<len; i++) {
      File f = (File) model.getElementAt(i);
      if (file.equals(f)) {
        listView.setSelectedIndex(i);
        listView.ensureIndexIsVisible(i);
        return;
      }
    }
  }

  public void updateTabInfo()
  {
    Component c = tabs.getSelectedComponent();
    if (c instanceof DviBrowserPage) {
      DviBrowserPage bp = (DviBrowserPage) c;
      statusBar.getDviProgressView().setProgressReporter(bp.getProgressReporter());
    }
  }

  protected void recordURL(URL url)
  {
    history.moveTo(url);
  }
  
  public void reload()
  {
    Component c = tabs.getSelectedComponent();
    if (c instanceof DviBrowserPage) {
      DviBrowserPage bp = (DviBrowserPage) c;
      try {
        bp.reload();
      } catch (DviException e) {
        e.printStackTrace();
      }
    }
  }

  public void actionPerformed(ActionEvent e)
  {
    String cmd = e.getActionCommand();
    if ("Exit".equals(cmd)) {
//      dispose();
      System.exit(0);
    } else if ("Open".equals(cmd)) {
      openByFileDialog();
    } else if ("Reload".equals(cmd)) {
      reload();
    }
  }
  
  protected void openByFileDialog()
  {
    JFileChooser chooser = new JFileChooser();
    int r = chooser.showOpenDialog(this);
    if (r == JFileChooser.APPROVE_OPTION) {
      File dir = chooser.getSelectedFile();
      try {
        open(dir);
      } catch (DviException e) {
        e.printStackTrace();
      }
    }
  }

  public void handleEvent(TEvent e)
  {
  }
  public DviFileListModel getDviFileListModel()
  {
    return model;
  }

  public void setDviFileListModel(DviFileListModel model)
  {
    this.model = model;
  }

  public JList getListView()
  {
    return listView;
  }

  public DviAddressBar getAddressBar()
  {
    return addressBar;
  }

  public DviURLHistory getHistory()
  {
    return history;
  }
  
  public void openURLOfAddressBar()
  {
    try {
      URL url = new URL(addressBar.getAddressEdit().getText());
      open(url);
    } catch (MalformedURLException e1) {
      e1.printStackTrace();
    }
  }

  public DviBrowserStatusBar getStatusBar()
  {
    return statusBar;
  }

}
