/*-
 * Copyright (C) 2008 Erik Larsson
 *
 * This program 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 org.catacombae.hfsexplorer.gui;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import org.catacombae.hfsexplorer.GUIUtil;
import org.catacombae.hfsexplorer.IOUtil;
import org.catacombae.util.Util;
import org.catacombae.hfsexplorer.fs.ResourceForkReader;
import org.catacombae.hfsexplorer.types.resff.ReferenceListEntry;
import org.catacombae.hfsexplorer.types.resff.ResourceMap;
import org.catacombae.hfsexplorer.types.resff.ResourceName;
import org.catacombae.hfsexplorer.types.resff.ResourceType;
import org.catacombae.io.ReadableRandomAccessStream;
import org.catacombae.util.Util.Pair;

/**
 * @author <a href="https://catacombae.org" target="_top">Erik Larsson</a>
 */
public class ResourceForkViewPanel extends javax.swing.JPanel {
    private ResourceForkReader reader = null;

    /**
     * An item as it is displayed in the list view over available resources.
     * Its toString method decides how it is displayed to the user.
     */
    private class ListItem {
        ResourceType type;
        ReferenceListEntry entry;
        ResourceName name;
        long size;

        public ListItem(ResourceType type,
                ReferenceListEntry entry,
                ResourceName name,
                long size) {
            this.type = type;
            this.entry = entry;
            this.name = name;
            this.size = size;
        }

        @Override
        public String toString() {
            try {
                StringBuilder sb = new StringBuilder();
                sb.append(new String(type.getType(), "MacRoman"));

                if(name != null)
                    sb.append(" \"").append(new String(name.getName(), "MacRoman")).append("\"");
                return sb.toString();
            } catch(Exception e) {
                e.printStackTrace();
                return "{" + e.getClass().getSimpleName() + " in resource id " + entry.getResourceID() + "}";
            }
        }
    }

    /** Creates new form ResourceForkViewPanel */
    public ResourceForkViewPanel(ResourceForkReader startupReader) {
        initComponents();
        resourceList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

        loadResourceFork(startupReader);

        resourceList.addListSelectionListener(new ListSelectionListener() {

            public void valueChanged(ListSelectionEvent e) {
                Object o = resourceList.getSelectedValue();
                if(o instanceof ListItem)
                    setSelectedItem((ListItem)o);
                else if(o != null)
                    JOptionPane.showMessageDialog(resourceList, "Unexpected type in list: " + o.getClass());
            }
        });

        viewButton.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                Object selection = resourceList.getSelectedValue();
                if(selection != null && selection instanceof ListItem) {
                    ListItem selectedItem = (ListItem) selection;
                    JDialog d = new JDialog(JOptionPane.getFrameForComponent(ResourceForkViewPanel.this),
                            selection.toString(), true);

                    DisplayTextFilePanel dtfp = new DisplayTextFilePanel();
                    dtfp.loadStream(reader.getResourceStream(selectedItem.entry));

                    d.add(dtfp);
                    d.pack();
                    d.setLocationRelativeTo(null);
                    d.setVisible(true);
                }
            }

        });

        extractButton.addActionListener(new ActionListener() {
            private JFileChooser fileChooser = new JFileChooser();
            {
                fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
                fileChooser.setMultiSelectionEnabled(false);
            }
            public void actionPerformed(ActionEvent e) {
                Object selection = resourceList.getSelectedValue();
                if(selection != null && selection instanceof ListItem) {
                    ListItem selectedItem = (ListItem) selection;

                    if(fileChooser.showSaveDialog(ResourceForkViewPanel.this) == JFileChooser.APPROVE_OPTION) {
                        File saveFile = fileChooser.getSelectedFile();
                        if(saveFile.exists()) {
                            int res = JOptionPane.showConfirmDialog(ResourceForkViewPanel.this,
                                    "The file already exists. Do you want to overwrite?",
                                    "Confirm overwrite", JOptionPane.YES_NO_OPTION,
                                    JOptionPane.QUESTION_MESSAGE);
                            if(res != JOptionPane.YES_OPTION)
                                return;
                        }

                        ReadableRandomAccessStream in = null;
                        FileOutputStream fos = null;
                        try {
                            in = reader.getResourceStream(selectedItem.entry);
                            fos = new FileOutputStream(saveFile);

                            IOUtil.streamCopy(in, fos, 65536);
                        } catch(FileNotFoundException fnfe) {
                            JOptionPane.showMessageDialog(ResourceForkViewPanel.this,
                                    "Could not open file \"" + saveFile.getPath() + "\" for writing...",
                                    "Error", JOptionPane.ERROR_MESSAGE);
                        } catch(IOException ioe) {
                            ioe.printStackTrace();
                            GUIUtil.displayExceptionDialog(ioe, ResourceForkViewPanel.this);
                        } finally {
                            if(in != null)
                                in.close();
                            if(fos != null) {
                                try {
                                    fos.close();
                                } catch(IOException ex) {
                                    ex.printStackTrace();
                                    GUIUtil.displayExceptionDialog(ex, ResourceForkViewPanel.this);
                                }
                            }
                        }
                    }
                }
            }

        });
    }

    public final void loadResourceFork(ResourceForkReader reader) {
        if(reader != null) {
            ListItem[] allItems = listAllItems(reader);
            resourceList.setEnabled(true);
            resourceList.setListData(allItems);
            resourceListLabel.setText("Resource list (" + allItems.length + " items):");
        }
        else {
            resourceList.setEnabled(false);
            resourceList.setListData(new Object[0]);
            resourceListLabel.setText("Resource list:");
        }
        setSelectedItem(null);
        this.reader = reader;
    }

    private ListItem[] listAllItems(ResourceForkReader reader) {
        //System.err.println("listAllItems(): getting resource map");
        ResourceMap resMap = reader.getResourceMap();

        LinkedList<ListItem> result = new LinkedList<ListItem>();

        //System.err.println("listAllItems(): getting reference list for " + resMap);
        List<Pair<ResourceType, ReferenceListEntry[]>> refList = resMap.getReferenceList();
        for(Pair<ResourceType, ReferenceListEntry[]> p : refList) {
            ResourceType type = p.getA();
            for(ReferenceListEntry entry : p.getB()) {
                //System.err.println("listAllItems(): getting name by reflist entry " + entry);
                ResourceName name = resMap.getNameByReferenceListEntry(entry);
                long size = reader.getDataLength(entry);

                result.add(new ListItem(type, entry, name, size));
            }
        }

        return result.toArray(new ListItem[result.size()]);
    }

    private void setSelectedItem(ListItem li) {
        final boolean enabled;
        if(li == null)
            enabled = false;
        else
            enabled = true;

        extractButton.setEnabled(enabled);
        viewButton.setEnabled(enabled);
        nameField.setEnabled(enabled);
        typeField.setEnabled(enabled);
        idField.setEnabled(enabled);
        sizeField.setEnabled(enabled);
        attributesField.setEnabled(enabled);

        if(!enabled) {
            nameField.setText("");
            typeField.setText("");
            idField.setText("");
            sizeField.setText("");
            attributesField.setText("");
        }
        else {
            String nameString;
            if(li.name != null) {
                try {
                    nameString = new String(li.name.getName(), "MacRoman");
                } catch(Exception e) {
                    e.printStackTrace();
                    nameString = "[Could not decode: " + e.toString() + "]";
                }
            }
            else {
                nameString = null;
            }

            String typeString;
            try {
                typeString = new String(li.type.getType(), "MacRoman");
            } catch(Exception e) {
                e.printStackTrace();
                typeString = "[Could not decode: " + e.toString() + "]";
            }

            if(nameField == null) {
                nameField.setEnabled(false);
                nameField.setName("");
            }
            else
                nameField.setText(nameString);

            typeField.setText(typeString);
            idField.setText("" + li.entry.getResourceID());
            sizeField.setText(li.size + " bytes");
            attributesField.setText("0x" + Util.toHexStringBE(li.entry.getResourceAttributes()));
        }
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        resourceListLabel = new javax.swing.JLabel();
        resourceListScroller = new javax.swing.JScrollPane();
        resourceList = new javax.swing.JList();
        fieldsPanel = new javax.swing.JPanel();
        nameLabel = new javax.swing.JLabel();
        nameField = new javax.swing.JTextField();
        typeLabel = new javax.swing.JLabel();
        typeField = new javax.swing.JTextField();
        idLabel = new javax.swing.JLabel();
        idField = new javax.swing.JTextField();
        sizeLabel = new javax.swing.JLabel();
        sizeField = new javax.swing.JTextField();
        attributesLabel = new javax.swing.JLabel();
        attributesField = new javax.swing.JTextField();
        extractButton = new javax.swing.JButton();
        viewButton = new javax.swing.JButton();

        resourceListLabel.setText("[This label is set programmatically]");

        resourceList.setModel(new javax.swing.AbstractListModel() {
            String[] strings = { "Item 1", "Item 2", "Item 3", "Item 4", "Item 5" };
            public int getSize() { return strings.length; }
            public Object getElementAt(int i) { return strings[i]; }
        });
        resourceListScroller.setViewportView(resourceList);

        nameLabel.setText("Name:");

        nameField.setEditable(false);
        nameField.setText("jTextField1");
        nameField.setOpaque(false);

        typeLabel.setText("Type:");

        typeField.setEditable(false);
        typeField.setText("jTextField2");
        typeField.setOpaque(false);

        idLabel.setText("ID:");

        idField.setEditable(false);
        idField.setText("jTextField3");
        idField.setOpaque(false);

        sizeLabel.setText("Size:");

        sizeField.setEditable(false);
        sizeField.setText("jTextField4");
        sizeField.setOpaque(false);

        attributesLabel.setText("Attributes:");

        attributesField.setEditable(false);
        attributesField.setText("jTextField5");
        attributesField.setOpaque(false);

        org.jdesktop.layout.GroupLayout fieldsPanelLayout = new org.jdesktop.layout.GroupLayout(fieldsPanel);
        fieldsPanel.setLayout(fieldsPanelLayout);
        fieldsPanelLayout.setHorizontalGroup(
            fieldsPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(fieldsPanelLayout.createSequentialGroup()
                .add(fieldsPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                    .add(nameLabel)
                    .add(typeLabel)
                    .add(idLabel)
                    .add(sizeLabel)
                    .add(attributesLabel))
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(fieldsPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                    .add(attributesField, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 227, Short.MAX_VALUE)
                    .add(sizeField, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 227, Short.MAX_VALUE)
                    .add(idField, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 227, Short.MAX_VALUE)
                    .add(nameField, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 227, Short.MAX_VALUE)
                    .add(typeField, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 227, Short.MAX_VALUE)))
        );
        fieldsPanelLayout.setVerticalGroup(
            fieldsPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(fieldsPanelLayout.createSequentialGroup()
                .add(fieldsPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
                    .add(nameLabel)
                    .add(nameField, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(fieldsPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
                    .add(typeLabel)
                    .add(typeField, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(fieldsPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
                    .add(idLabel)
                    .add(idField, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(fieldsPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
                    .add(sizeLabel)
                    .add(sizeField, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(fieldsPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
                    .add(attributesLabel)
                    .add(attributesField, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)))
        );

        extractButton.setText("Save to file...");

        viewButton.setText("View as text");

        org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(org.jdesktop.layout.GroupLayout.TRAILING, layout.createSequentialGroup()
                .addContainerGap()
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING)
                    .add(org.jdesktop.layout.GroupLayout.LEADING, resourceListScroller, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 283, Short.MAX_VALUE)
                    .add(org.jdesktop.layout.GroupLayout.LEADING, resourceListLabel)
                    .add(layout.createSequentialGroup()
                        .add(viewButton)
                        .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                        .add(extractButton))
                    .add(org.jdesktop.layout.GroupLayout.LEADING, fieldsPanel, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(layout.createSequentialGroup()
                .addContainerGap()
                .add(resourceListLabel)
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(resourceListScroller, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 99, Short.MAX_VALUE)
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(fieldsPanel, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
                    .add(extractButton)
                    .add(viewButton))
                .addContainerGap())
        );
    }// </editor-fold>//GEN-END:initComponents


    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JTextField attributesField;
    private javax.swing.JLabel attributesLabel;
    private javax.swing.JButton extractButton;
    private javax.swing.JPanel fieldsPanel;
    private javax.swing.JTextField idField;
    private javax.swing.JLabel idLabel;
    private javax.swing.JTextField nameField;
    private javax.swing.JLabel nameLabel;
    private javax.swing.JList resourceList;
    private javax.swing.JLabel resourceListLabel;
    private javax.swing.JScrollPane resourceListScroller;
    private javax.swing.JTextField sizeField;
    private javax.swing.JLabel sizeLabel;
    private javax.swing.JTextField typeField;
    private javax.swing.JLabel typeLabel;
    private javax.swing.JButton viewButton;
    // End of variables declaration//GEN-END:variables

}
