/*
 * Copyright (C) 2007-2009 KenD00
 * 
 * This file is part of DumpHD.
 * 
 * DumpHD is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * 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, see <http://www.gnu.org/licenses/>.
 */
package dumphd.gui;

import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.io.IOException;

import javax.swing.JOptionPane;
import javax.swing.ListSelectionModel;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ScrollPaneConstants;

import dumphd.core.DiscSet;
import dumphd.core.KeyData;
import dumphd.util.Utils;

/**
 * This class represents a tab for a specific disc type.
 * It holds the reference to the underlying disc set object, all graphical elements make changes directly to that object.
 * 
 * The JComponent to display this tab and the tab title and icon are also hold by this class.
 *
 * FIXME: Don't use DiscSet.contentType as Index!!!!! 
 * 
 * @author KenD00
 */
public class SourceTab {

   private final static String NA_STRING = "N/A";
   private final static String OK_STRING = "OK";
   private final static String CANCEL_STRING = "Cancel";
   private final static String SAVE_STRING = "Save";

   /**
    * The titles of the tabs. Index is the disc type (see DiscSet)
    */
   private final static String[] TAB_TITLES = new String[8];
   /**
    * The icons of the tabs. Index is the disc type (see DiscSet)
    */
   private final static Icon[] TAB_ICONS = new Icon[8];

   private Manager manager = null;
   /**
    * The underlying disc set object this SourceTab represents
    */
   private DiscSet ds = null;
   /**
    * If true, the user can not interact with the object
    */
   private boolean locked = false;

   /**
    * The title for this tab
    */
   private String tabTitle = null;
   /**
    * The icon for this tab
    */
   private Icon tabIcon = null;
   /**
    * The tab contents
    */
   private JPanel tabPanel = null;
   /**
    * DiscID button
    */
   private JButton discIdButton = null;
   /**
    * DiscID label
    */
   private JLabel discIdLabel = null;
   /**
    * Title button
    */
   private JButton titleButton = null;
   /**
    * Title label
    */
   private JLabel titleLabel = null;
   /**
    * The table that displays the files
    */
   private JTable fileTable = null;

   /**
    * If true, it is allowed to change the DiscID
    */
   private boolean discIdEditable = false;
   /**
    * If true, it is allowed to change the Title
    */
   private boolean titleEditable = false;
   /**
    * If true, the file table can be used to modify the file set 
    */
   private boolean fileTableEditable = false;


   static {
      SourceTab.TAB_TITLES[0] = SourceTab.NA_STRING;
      SourceTab.TAB_TITLES[1] = "Standard Video";
      SourceTab.TAB_TITLES[2] = "Standard Audio";
      SourceTab.TAB_TITLES[3] = "Advanced Video";
      SourceTab.TAB_TITLES[4] = "Advanced Audio";
      SourceTab.TAB_TITLES[5] = "BDMV";
      SourceTab.TAB_TITLES[6] = "BDMV (Recordable)";
      SourceTab.TAB_TITLES[7] = "BDAV (Recordable)";
      SourceTab.TAB_ICONS[0] = new ImageIcon(Manager.getResource("icons/empty_tab.png"));
      SourceTab.TAB_ICONS[1] = new ImageIcon(Manager.getResource("icons/hdsc_tab.png"));
      SourceTab.TAB_ICONS[2] = new ImageIcon(Manager.getResource("icons/hdsc_tab.png"));
      SourceTab.TAB_ICONS[3] = new ImageIcon(Manager.getResource("icons/hdac_tab.png"));
      SourceTab.TAB_ICONS[4] = new ImageIcon(Manager.getResource("icons/hdac_tab.png"));
      SourceTab.TAB_ICONS[5] = new ImageIcon(Manager.getResource("icons/bd_tab.png"));
      SourceTab.TAB_ICONS[6] = new ImageIcon(Manager.getResource("icons/bd_tab.png"));
      SourceTab.TAB_ICONS[7] = new ImageIcon(Manager.getResource("icons/bd_tab.png"));
   }


   /**
    * Creates an empty source tab
    */
   public SourceTab(Manager manager) {
      this.manager = manager;
      createElements();
   }

   /**
    * Creates a tab from the given DiscSet.
    * 
    * @param ws DiscSet to create the tab for
    */
   public SourceTab(Manager manager, DiscSet ws) {
      this.manager = manager;
      this.ds = ws;
      createElements();
      init(ws);
   }

   /**
    * Creates all elements for this object.
    */
   private void createElements() {
      // Tab title and icon
      tabTitle = SourceTab.TAB_TITLES[0];
      tabIcon = SourceTab.TAB_ICONS[0];
      // Tab content panel
      tabPanel = new JPanel();
      tabPanel.setOpaque(true);
      tabPanel.setBorder(Manager.createEmptyBorder());
      tabPanel.setLayout(new BoxLayout(tabPanel, BoxLayout.PAGE_AXIS));
      JPanel panel = null;
      // DiscID and Title elements
      ButtonListener buttonListener = new ButtonListener();
      discIdButton = new JButton("DiscID");
      discIdButton.setEnabled(discIdEditable);
      discIdButton.setActionCommand("discid");
      discIdButton.addActionListener(buttonListener);
      titleButton = new JButton("Title");
      titleButton.setEnabled(titleEditable);
      titleButton.setActionCommand("title");
      titleButton.addActionListener(buttonListener);
      discIdLabel = new JLabel(SourceTab.NA_STRING);
      discIdLabel.setBorder(Manager.createLabelBorder());
      discIdLabel.setToolTipText(SourceTab.NA_STRING);
      titleLabel = new JLabel(SourceTab.NA_STRING);
      titleLabel.setBorder(Manager.createLabelBorder());
      titleLabel.setToolTipText(SourceTab.NA_STRING);
      Manager.equalizeLabelButtonHeight(discIdLabel, discIdButton);
      Manager.equalizeLabelButtonHeight(titleLabel, titleButton);
      Manager.equalizeComponentSize(discIdButton, titleButton);
      // DiscId and Title panels
      panel = new JPanel();
      panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS));
      panel.add(discIdButton);
      panel.add(Box.createRigidArea(Manager.BOX_HORIZONTAL_BIG_SPACING));
      panel.add(discIdLabel);
      tabPanel.add(panel);
      tabPanel.add(Box.createRigidArea(Manager.BOX_VERTICAL_SMALL_SPACING));
      panel = new JPanel();
      panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS));
      panel.add(titleButton);
      panel.add(Box.createRigidArea(Manager.BOX_HORIZONTAL_BIG_SPACING));
      panel.add(titleLabel);
      tabPanel.add(panel);
      tabPanel.add(Box.createRigidArea(Manager.BOX_VERTICAL_SMALL_SPACING));
      // File table
      fileTable = new JTable(new FileTableModel());
      fileTable.setAutoCreateColumnsFromModel(false);
      fileTable.setAutoResizeMode(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS);
      fileTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
      fileTable.setShowGrid(false);
      fileTable.getTableHeader().setReorderingAllowed(false);
      fileTable.setEnabled(fileTableEditable);
      JScrollPane fileScroller = new JScrollPane(fileTable, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
      tabPanel.add(fileScroller);
   }

   /**
    * Initializes this object with the values from the given DiscSet.
    * There is no need to clear this object prior calling this method.
    * 
    * @param ds The DiscSet this tab is initialized for
    */
   public void init(DiscSet ds) {
      this.ds = ds;
      tabTitle = SourceTab.TAB_TITLES[ds.contentType];
      tabIcon = SourceTab.TAB_ICONS[ds.contentType];
      if (ds.keyData != null) {
         byte[] discId = ds.keyData.getDiscId();
         String title = ds.keyData.getTitle();
         discIdLabel.setText(Utils.toHexString(discId, 0, discId.length));
         discIdLabel.setToolTipText(discIdLabel.getText());
         if (title != null) {
            titleLabel.setText(title);
            // Check for an empty string and set it to space to avoid an ugly ToolTip-Display bug
            if (title.equals("")) {
               titleLabel.setToolTipText(" ");
            } else {
               titleLabel.setToolTipText(title);
            }
            titleEditable = true;
         }
         else {
            titleLabel.setText(SourceTab.NA_STRING);
            titleLabel.setToolTipText(SourceTab.NA_STRING);
            titleEditable = false;
         }
         discIdEditable = true;
      }
      else {
         discIdLabel.setText(SourceTab.NA_STRING);
         discIdLabel.setToolTipText(SourceTab.NA_STRING);
         titleLabel.setText(SourceTab.NA_STRING);
         titleLabel.setToolTipText(SourceTab.NA_STRING);
         discIdEditable = false;
         titleEditable = false;
      }
      ((FileTableModel)fileTable.getModel()).setData(ds);
      fileTableEditable = true;
      updateEnabledState(false);
   }

   /**
    * Clears all data from this object.
    * After this method call this object is an empty tab.
    */
   public void clear() {
      ds = null;
      tabTitle = SourceTab.TAB_TITLES[0];
      tabIcon = SourceTab.TAB_ICONS[0];
      discIdLabel.setText(SourceTab.NA_STRING);
      discIdLabel.setToolTipText(SourceTab.NA_STRING);
      titleLabel.setText(SourceTab.NA_STRING);
      titleLabel.setToolTipText(SourceTab.NA_STRING);
      discIdEditable = false;
      titleEditable = false;
      ((FileTableModel)fileTable.getModel()).setData(null);
      fileTableEditable = false;
      updateEnabledState(false);
   }

   /**
    * Updates the enabled state of the elements the user can interact with.
    * Call this method if the data describing the enabled state of the elements has changed.
    * This method is also used to lock and unlock this object.
    * 
    * @param forced If true, the enabled state is set forcibly, even if its not necessary (e.g. the object is locked). Set this to true if the object should be actually locked or unlocked.
    */
   private void updateEnabledState(boolean forced) {
      if (!locked) {
         discIdButton.setEnabled(discIdEditable);
         titleButton.setEnabled(titleEditable);
         fileTable.setEnabled(fileTableEditable);
      }
      else {
         if (forced) {
            discIdButton.setEnabled(false);
            titleButton.setEnabled(false);
            fileTable.setEnabled(false);
         }
      }
   }

   /**
    * Locks this object. The user can not interact with it any more.
    */
   public void lock() {
      locked = true;
      updateEnabledState(true);
   }

   /**
    * Unlock this object. The user can interact with this object now.
    */
   public void unlock() {
      locked = false;
      updateEnabledState(true);
   }

   /**
    * Returns the locked state of this object.
    * 
    * @return If true, this object is locked, otherwise not
    */
   public boolean isLocked() {
      return locked;
   }

   /**
    * Returns the title of this object.
    * 
    * @return The title for this tab
    */
   public String getTabTitle() {
      return tabTitle;
   }

   /**
    * Returns the icon of this object.
    * 
    * @return The Icon for this tab
    */
   public Icon getTabIcon() {
      return tabIcon;
   }

   /**
    * Returns the content of this object.
    * 
    * @return The JComponent for this tabs content
    */
   public JComponent getTabComponent() {
      return tabPanel;
   }

   /**
    * Call this method if the Look & Feel has changed. Resizes all elements that have fixed sizes.
    */
   public void updateFixedSizedComponentsSize() {
      discIdButton.setMinimumSize(null);
      discIdButton.setMaximumSize(null);
      discIdButton.setPreferredSize(null);
      discIdLabel.setMinimumSize(null);
      discIdLabel.setMaximumSize(null);
      discIdLabel.setPreferredSize(null);
      titleButton.setMinimumSize(null);
      titleButton.setMaximumSize(null);
      titleButton.setPreferredSize(null);
      titleLabel.setMinimumSize(null);
      titleLabel.setMaximumSize(null);
      titleLabel.setPreferredSize(null);
      Manager.equalizeLabelButtonHeight(discIdLabel, discIdButton);
      Manager.equalizeLabelButtonHeight(titleLabel, titleButton);
      Manager.equalizeComponentSize(discIdButton, titleButton);
      // Invalidate a element in each panel to force it to relayout
      discIdButton.invalidate();
      titleButton.invalidate();
      fileTable.invalidate();
   }

   private class ButtonListener implements ActionListener {

      public void actionPerformed(ActionEvent e) {
         String command = e.getActionCommand();
         if (command.equals("discid")) {
            ValidatedInputDialog dialog = new ValidatedInputDialog(manager.getMainFrame().getFrame(), "Override DiscID", "Warning! Override the DiscID only if the disc contents were wrongly identified!", "DiscID", discIdLabel.getText(), 40, "[0-9a-fA-F]{40}", "The DiscID must be 40 characters long and may only contain the characters 0-9, a-f, A-F", SourceTab.OK_STRING, SourceTab.CANCEL_STRING);
            if (dialog.show()) {
               final String discIdString = dialog.getText();
               // This ManagedJob returns the found KeyData object or null if not found
               //TODO: Implement a way to retrieve keys without using AACSKeys?
               ManagedJob workload = new ManagedJob() {
                  public Object run(Object input) {
                     byte[] discId = new byte[20];
                     Utils.decodeHexString(discIdString, discId, 0);
                     try {
                        return SourceTab.this.manager.getDumpHD().getKeyDataFile().getKeyData(discId, 0);
                     }
                     catch (IOException e) {
                        SourceTab.this.manager.getMainFrame().getMessagePrinter().println(e.getMessage());
                        return null;
                     }
                  }
               };
               ManagedJob guiJob = new ManagedJob() {
                  public Object run(Object input) {
                     if (input != null) {
                        SourceTab.this.manager.getMainFrame().getMessagePrinter().println("Disc found in database");
                        ds.keyData = (KeyData)input;
                        discIdLabel.setText(discIdString);
                        discIdLabel.setToolTipText(discIdString);
                        String title = ds.keyData.getTitle();
                        //TODO: Title should be always non-null if retrieved from key database
                        if (title != null) {
                           titleLabel.setText(title);
                           // Check for an empty string and set it to space to avoid an ugly ToolTip-Display bug
                           if (title.equals("")) {
                              titleLabel.setToolTipText(" ");
                           } else {
                              titleLabel.setToolTipText(title);
                           }
                           titleEditable = true;
                        } else {
                           titleLabel.setText(SourceTab.NA_STRING);
                           titleLabel.setToolTipText(SourceTab.NA_STRING);
                           titleEditable = false;
                        }
                        updateEnabledState(false);
                     } else {
                        SourceTab.this.manager.getMainFrame().getMessagePrinter().println("Disc not found in database");
                        JOptionPane.showMessageDialog(SourceTab.this.manager.getMainFrame().getFrame(), "DiscID not found in key database, DiscID not changed!", "Error", JOptionPane.ERROR_MESSAGE);
                     }
                     SourceTab.this.manager.getMainFrame().unlock();
                     return null;
                  }
               };
               manager.getMainFrame().getMessagePrinter().println("Searching disc in key database...");
               SourceTab.this.manager.getMainFrame().lock();
               SourceTab.this.manager.execute(workload, guiJob);
            }
         } else if (command.equals("title")) {
            //TODO: allow empty title?
            ValidatedInputDialog dialog = new ValidatedInputDialog(manager.getMainFrame().getFrame(), "Edit movie title", "Saving the title causes an immediate update of the key database", "Title", titleLabel.getText(), 48, "[^|\\r\\n]+", "The Title must be at least one character long and must not contain the character |", SourceTab.SAVE_STRING, SourceTab.CANCEL_STRING);
            if (dialog.show()) {
               final String title = dialog.getText();
               ds.keyData.setTitle(title);
               // This ManagedJob returns the found old KeyData object or null if not found and updated
               ManagedJob workload = new ManagedJob() {
                  public Object run(Object input) {
                     try {
                        return SourceTab.this.manager.getDumpHD().getKeyDataFile().setKeyData(ds.keyData, ds.isBluRay(), ds.isRecordable(), true);
                     }
                     catch (IOException e) {
                        SourceTab.this.manager.getMainFrame().getMessagePrinter().println(e.getMessage());
                        return null;
                     }
                  }
               };
               ManagedJob guiJob = new ManagedJob() {
                  public Object run(Object input) {
                     if (input != null) {
                        SourceTab.this.manager.getMainFrame().getMessagePrinter().println("Entry updated");
                        titleLabel.setText(ds.keyData.getTitle());
                        titleLabel.setToolTipText(ds.keyData.getTitle());
                     } else {
                        SourceTab.this.manager.getMainFrame().getMessagePrinter().println("Entry not updated");
                        ds.keyData.setTitle(titleLabel.getText());
                        JOptionPane.showMessageDialog(SourceTab.this.manager.getMainFrame().getFrame(), "Error updating entry in key database, see log for details", "Error", JOptionPane.ERROR_MESSAGE);
                     }
                     SourceTab.this.manager.getMainFrame().unlock();
                     return null;
                  }
               };
               manager.getMainFrame().getMessagePrinter().println("Updating entry in key database...");
               SourceTab.this.manager.getMainFrame().lock();
               SourceTab.this.manager.execute(workload, guiJob);
            }
         }
      }
   }

}
