/* ====================================================================
   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
   this work for additional information regarding copyright ownership.
   The ASF licenses this file to You under the Apache License, Version 2.0
   (the "License"); you may not use this file except in compliance with
   the License.  You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
==================================================================== */

package org.apache.poi.hssf.view;

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

import org.apache.poi.hssf.usermodel.*;

/**
 * This class presents the sheets to the user.
 */
public class SViewerPanel extends JPanel {
  /** This field is the magic number to convert from a Character width to a
   *  java pixel width.
   *
   * When the "normal" font size in a workbook changes, this effects all
   * of the heights and widths. Unfortunately there is no way to retrieve this
   * information, hence the MAGIC number.
   *
   * This number may only work for the normal style font size of Arial size 10.
   *
   */
  private static final int magicCharFactor = 7;
  /** Reference to the wookbook that is being displayed*/
  /* package */ HSSFWorkbook wb;
  /** Reference to the tabs component*/
  /* package */ JTabbedPane sheetPane;
  /** Reference to the cell renderer that is used to render all cells*/
  private SVTableCellRenderer cellRenderer;
  /** Reference to the cell editor that is used to edit all cells.
   *  Only constructed if editing is allowed
   */
  private SVTableCellEditor cellEditor;
  /** Flag indicating if editing is allowed. Otherwise the viewer is in
   *  view only mode.
   */
  private boolean allowEdits;

  /**Construct the representation of the workbook*/
  public SViewerPanel(HSSFWorkbook wb, boolean allowEdits) {
    this.wb = wb;
    this.allowEdits = allowEdits;

    initialiseGui();
  }

  private void initialiseGui() {
    cellRenderer = new SVTableCellRenderer(this.wb);
    if (allowEdits)
      cellEditor = new SVTableCellEditor(this.wb);

    //Initialise the Panel
    sheetPane = new JTabbedPane(JTabbedPane.BOTTOM);

    if (allowEdits)
      sheetPane.addMouseListener(createTabListener());
    int sheetCount = wb.getNumberOfSheets();
    for (int i=0; i<sheetCount;i++) {
      String sheetName = wb.getSheetName(i);
      //Add the new sheet to the tabbed pane
      sheetPane.addTab(sheetName, makeSheetView(wb.getSheetAt(i)));
    }
    setLayout(new BorderLayout());
    add(sheetPane, BorderLayout.CENTER);
  }

  protected JComponent makeSheetView(HSSFSheet sheet) {
    JTable sheetView = new JTable(new SVTableModel(sheet));
    sheetView.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
    sheetView.setDefaultRenderer(HSSFCell.class, cellRenderer);
    if (allowEdits)
      sheetView.setDefaultEditor(HSSFCell.class, cellEditor);
    JTableHeader header = sheetView.getTableHeader();
    //Dont allow column reordering
    header.setReorderingAllowed(false);
    //Only allow column resizing if editing is allowed
    header.setResizingAllowed(allowEdits);

    //Set the columns the correct size
    TableColumnModel columns = sheetView.getColumnModel();
    for (int i=0; i< columns.getColumnCount(); i++) {
      TableColumn column = columns.getColumn(i);
      int width = sheet.getColumnWidth(i);
      //256 is because the width is in 256ths of a character
      column.setPreferredWidth(width/256*magicCharFactor);
    }

    //Set the rows to the correct size
    int rows = sheet.getPhysicalNumberOfRows();
    Insets insets = cellRenderer.getInsets();
    //Need to include the insets in the calculation of the row height to use.
    int extraHeight = insets.bottom+insets.top;
    for (int i=0; i< rows; i++) {
      HSSFRow row = sheet.getRow(i);
      if (row == null) {
        sheetView.setRowHeight(i, (int)sheet.getDefaultRowHeightInPoints()+extraHeight);
      } else {
        sheetView.setRowHeight(i, (int)row.getHeightInPoints()+extraHeight);
      }
    }

    //Add the row header to the sheet
    SVRowHeader rowHeader = new SVRowHeader(sheet, sheetView, extraHeight);
    JScrollPane scroll = new JScrollPane( sheetView );
    scroll.setRowHeaderView(rowHeader);
    return scroll;
  }

  @Override
public void paint(Graphics g) {
    //JMH I am only overriding this to get a picture of the time taken to paint
    long start = System.currentTimeMillis();
    super.paint(g);
    long elapsed = System.currentTimeMillis()-start;
    System.out.println("Paint time = "+elapsed);
  }

  protected MouseListener createTabListener() {
    return new TabListener();
  }

  /** This class defines the default MouseListener that listens to
   *  mouse events in the tabbed pane
   *
   *  The default is to popup a menu when the event occurs over a tab
   */
  private class TabListener implements MouseListener {
    public JPopupMenu popup;
    public TabListener() {
      popup = new JPopupMenu("Sheet");
      popup.add(createInsertSheetAction());
      popup.add(createDeleteSheetAction());
      popup.add(createRenameSheetAction());
    }

    protected Action createInsertSheetAction() {
      return new InsertSheetAction();
    }

    protected Action createDeleteSheetAction() {
      return new DeleteSheetAction();
    }

    protected Action createRenameSheetAction() {
      return new RenameSheetAction();
    }


    /** This method will display the popup if the mouseevent is a popup event
     *  and the event occurred over a tab
     */
    protected void checkPopup(MouseEvent e) {
      if (e.isPopupTrigger()) {
        int tab = sheetPane.getUI().tabForCoordinate(sheetPane, e.getX(), e.getY());
        if (tab != -1) {
          popup.show(sheetPane, e.getX(), e.getY());
        }
      }
    }

    @Override
    public void mouseClicked(MouseEvent e) {
      checkPopup(e);
    }

    @Override
    public void mousePressed(MouseEvent e) {
      checkPopup(e);
    }

    @Override
    public void mouseReleased(MouseEvent e) {
      checkPopup(e);
    }

    @Override
    public void mouseEntered(MouseEvent e) {}
    @Override
    public void mouseExited(MouseEvent e) {}
  }

  /** This class defines the action that is performed when the sheet is renamed*/
  private class RenameSheetAction extends AbstractAction {
    public RenameSheetAction() {
      super("Rename");
    }

    @Override
    public void actionPerformed(ActionEvent e) {
      int tabIndex = sheetPane.getSelectedIndex();
      if (tabIndex != -1) {
        String newSheetName = JOptionPane.showInputDialog(sheetPane, "Enter a new Sheetname", "Rename Sheet", JOptionPane.QUESTION_MESSAGE);
        if (newSheetName != null) {
          wb.setSheetName(tabIndex, newSheetName);
          sheetPane.setTitleAt(tabIndex, newSheetName);
        }
      }
    }
  }

  /** This class defines the action that is performed when a sheet is inserted*/
  private class InsertSheetAction extends AbstractAction {
    public InsertSheetAction() {
      super("Insert");
    }

    @Override
    public void actionPerformed(ActionEvent e) {
      //Create a new sheet then search for the sheet and make sure that the
      //sheetPane shows it.
      HSSFSheet newSheet = wb.createSheet();
      for (int i=0; i<wb.getNumberOfSheets();i++) {
        HSSFSheet sheet = wb.getSheetAt(i);
        if (newSheet == sheet) {
          sheetPane.insertTab(wb.getSheetName(i), null, makeSheetView(sheet), null, i);
        }
      }
    }
  }

  /** This class defines the action that is performed when the sheet is deleted*/
  private class DeleteSheetAction extends AbstractAction {
    public DeleteSheetAction() {
      super("Delete");
    }

    @Override
    public void actionPerformed(ActionEvent e) {
      int tabIndex = sheetPane.getSelectedIndex();
      if (tabIndex != -1) {
        if (JOptionPane.showConfirmDialog(sheetPane, "Are you sure that you want to delete the selected sheet", "Delete Sheet?", JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION) {
          wb.removeSheetAt(tabIndex);
          sheetPane.remove(tabIndex);
        }
      }
    }
  }

  public boolean isEditable() {
    return allowEdits;
  }

  /**Main method*/
  public static void main(String[] args) throws IOException {
    if (args.length < 1) {
      throw new IllegalArgumentException("A filename to view must be supplied as the first argument, but none was given");
    }

    try (FileInputStream in = new FileInputStream(args[0]);
         HSSFWorkbook wb = new HSSFWorkbook(in)) {
      SViewerPanel p = new SViewerPanel(wb, true);
      JFrame frame;
      frame = new JFrame() {
        @Override
        protected void processWindowEvent(WindowEvent e) {
          super.processWindowEvent(e);
          if (e.getID() == WindowEvent.WINDOW_CLOSING) {
            System.exit(0);
          }
        }

        @Override
        public synchronized void setTitle(String title) {
          super.setTitle(title);
          enableEvents(AWTEvent.WINDOW_EVENT_MASK);
        }
      };
      frame.setTitle("Viewer Frame");
      frame.getContentPane().add(p, BorderLayout.CENTER);
      frame.setSize(800, 640);
      Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
      frame.setLocation((d.width - frame.getSize().width) / 2, (d.height - frame.getSize().height) / 2);
      frame.setVisible(true);
    }
  }
}
