/*
 * This file is part of muCommander, http://www.mucommander.com
 *
 * muCommander 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.
 *
 * muCommander 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 com.mucommander.ui.dialog.file;

import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;

import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;

import com.mucommander.commons.file.AbstractFile;
import com.mucommander.commons.file.PermissionAccess;
import com.mucommander.commons.file.PermissionType;
import com.mucommander.commons.file.util.FileSet;
import com.mucommander.commons.util.ui.dialog.DialogToolkit;
import com.mucommander.commons.util.ui.layout.YBoxPanel;
import com.mucommander.commons.util.ui.text.SizeConstrainedDocument;
import com.mucommander.desktop.ActionType;
import com.mucommander.job.impl.ChangeFileAttributesJob;
import com.mucommander.text.Translator;
import com.mucommander.ui.action.ActionProperties;
import com.mucommander.ui.action.impl.ChangePermissionsAction;
import com.mucommander.ui.main.MainFrame;

/**
 * This dialog allows the user to change the permissions of the currently selected/marked file(s). The permissions can be
 * selected either by clicking individual read/write/executable checkboxes for each of the user/group/other accesses,
 * or by entering an octal permission value. 
 *
 * @author Maxence Bernard
 */
public class ChangePermissionsDialog extends JobDialog
        implements ActionListener, ItemListener, DocumentListener {

    private JCheckBox permCheckBoxes[][];

    private JTextField octalPermTextField;

    private JCheckBox recurseDirCheckBox;

    /** If true, ItemEvent events should be ignored */
    private boolean ignoreItemEvent;
    /** If true, DocumentEvent events should be ignored */
    private boolean ignoreDocumentEvent;

    private JButton okButton;
    private JButton cancelButton;


    public ChangePermissionsDialog(MainFrame mainFrame, FileSet files) {
        super(mainFrame, ActionProperties.getActionLabel(ActionType.ChangePermissions), files);

        YBoxPanel mainPanel = new YBoxPanel();

        mainPanel.add(new JLabel(ActionProperties.getActionLabel(ActionType.ChangePermissions)+" :"));
        mainPanel.addSpace(10);

        JPanel gridPanel = new JPanel(new GridLayout(4, 4));
        permCheckBoxes = new JCheckBox[5][5];
        JCheckBox permCheckBox;

        AbstractFile firstFile = files.elementAt(0);
        int permSetMask = firstFile.getChangeablePermissions().getIntValue();
        boolean canSetPermission = permSetMask!=0;
        int defaultPerms = firstFile.getPermissions().getIntValue();

        gridPanel.add(new JLabel());
        gridPanel.add(new JLabel(Translator.get("permissions.read")));
        gridPanel.add(new JLabel(Translator.get("permissions.write")));
        gridPanel.add(new JLabel(Translator.get("permissions.executable")));

        for(PermissionAccess a : PermissionAccess.reverseValues()) {
            gridPanel.add(new JLabel(Translator.get(a==PermissionAccess.USER ?"permissions.user":a==PermissionAccess.GROUP?"permissions.group":"permissions.other")));

            for(PermissionType p : PermissionType.reverseValues()) {
                permCheckBox = new JCheckBox();
                permCheckBox.setSelected((defaultPerms & (p.toInt()<<a.toInt()*3))!=0);

                // Enable the checkbox only if the permission can be set in the destination
                if((permSetMask & (p.toInt()<<a.toInt()*3))==0)
                    permCheckBox.setEnabled(false);
                else
                    permCheckBox.addItemListener(this);

                gridPanel.add(permCheckBox);
                permCheckBoxes[a.toInt()][p.toInt()] = permCheckBox;
            }
        }

        mainPanel.add(gridPanel);

        octalPermTextField = new JTextField(3);
        // Constrains text field to 3 digits, from 0 to 7 (octal base)
        Document doc = new SizeConstrainedDocument(3) {
            @Override
            public void insertString(int offset, String str, AttributeSet attributeSet) throws BadLocationException {
                int strLen = str.length();
                char c;
                for(int i=0; i<strLen; i++) {
                    c = str.charAt(i);
                    if(c<'0' || c>'7')
                        return;
                }

                super.insertString(offset, str, attributeSet);
            }
        };
        octalPermTextField.setDocument(doc);
        // Initializes the field's value
        updateOctalPermTextField();

        if(canSetPermission) {
            doc.addDocumentListener(this);
        }
        // Disable text field if no permission bit can be set
        else {
            octalPermTextField.setEnabled(false);
        }

        mainPanel.addSpace(10);
        JPanel tempPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
        tempPanel.add(new JLabel(Translator.get("permissions.octal_notation")));
        tempPanel.add(octalPermTextField);
        mainPanel.add(tempPanel);

        mainPanel.addSpace(15);

        recurseDirCheckBox = new JCheckBox(Translator.get("recurse_directories"));
        // Disable check box if no permission bit can be set
        recurseDirCheckBox.setEnabled(canSetPermission && (files.size()>1 || files.elementAt(0).isDirectory()));
        mainPanel.add(recurseDirCheckBox);

        // Create file details button and OK/cancel buttons and lay them out a single row
        JPanel fileDetailsPanel = createFileDetailsPanel();

        okButton = new JButton(Translator.get("change"));
        cancelButton = new JButton(Translator.get("cancel"));

        mainPanel.add(createButtonsPanel(createFileDetailsButton(fileDetailsPanel),
                DialogToolkit.createOKCancelPanel(okButton, cancelButton, getRootPane(), this)));
        mainPanel.add(fileDetailsPanel);

        getContentPane().add(mainPanel, BorderLayout.NORTH);

        if(!canSetPermission) {
            // Disable OK button if no permission bit can be set
            okButton.setEnabled(false);
        }

        getRootPane().setDefaultButton(canSetPermission?okButton:cancelButton);
        setResizable(false);
    }


    /**
     * Creates and returns a permissions int using the values of the permission checkboxes.
     */
    private int getPermInt() {
        JCheckBox permCheckBox;
        int perms = 0;

        for(PermissionAccess a : PermissionAccess.reverseValues()) {
            for(PermissionType p : PermissionType.reverseValues()) {
                permCheckBox = permCheckBoxes[a.toInt()][p.toInt()];

                if(permCheckBox.isSelected())
                    perms |= (p.toInt()<<a.toInt()*3);
            }
        }

        return perms;
    }


    /**
     * Updates the octal permissions text field's value to reflect the permission checkboxes' values.
     */
    private void updateOctalPermTextField() {
        String octalStr = Integer.toOctalString(getPermInt());
        int len = octalStr.length();
        for(int i=len; i<3; i++)
            octalStr = "0"+octalStr;

        octalPermTextField.setText(octalStr);
    }


    /**
     * Updates the permission checkboxes' values to reflect the octal permissions text field.
     */
    private void updatePermCheckBoxes() {
        JCheckBox permCheckBox;
        String octalStr = octalPermTextField.getText();

        int perms = octalStr.equals("")?0:Integer.parseInt(octalStr, 8);

        for(PermissionAccess a : PermissionAccess.reverseValues()) {
            for(PermissionType p : PermissionType.reverseValues()) {
                permCheckBox = permCheckBoxes[a.toInt()][p.toInt()];

//                if(permCheckBox.isEnabled())
                permCheckBox.setSelected((perms & (p.toInt()<<a.toInt()*3))!=0);
            }
        }

    }


    ///////////////////////////////////
    // ActionListener implementation //
    ///////////////////////////////////

    public void actionPerformed(ActionEvent e) {
        Object source = e.getSource();

        if(source==okButton) {
            dispose();

            // Starts copying files
            ProgressDialog progressDialog = new ProgressDialog(mainFrame, Translator.get("progress_dialog.processing_files"));
            ChangeFileAttributesJob job = new ChangeFileAttributesJob(progressDialog, mainFrame, files, getPermInt(), recurseDirCheckBox.isSelected());
            progressDialog.start(job);
        }
        else if(source==cancelButton) {
            dispose();
        }
    }


    /////////////////////////////////
    // ItemListener implementation //
    /////////////////////////////////

    // Update the octal permission text field whenever one of the permission checkboxes' value has changed

    public void itemStateChanged(ItemEvent e) {
        if(ignoreItemEvent)
            return;

        ignoreDocumentEvent = true;
        updateOctalPermTextField();
        ignoreDocumentEvent = false;
    }


    //////////////////////////////
    // DocumentListener methods //
    //////////////////////////////

    // Update the permission checkboxes' values whenever the octal permission text field has changed

    public void changedUpdate(DocumentEvent e) {
        if(ignoreDocumentEvent)
            return;

        ignoreItemEvent = true;
        updatePermCheckBoxes();
        ignoreItemEvent = false;
    }

    public void insertUpdate(DocumentEvent e) {
        if(ignoreDocumentEvent)
            return;

        ignoreItemEvent = true;
        updatePermCheckBoxes();
        ignoreItemEvent = false;
    }

    public void removeUpdate(DocumentEvent e) {
        if(ignoreDocumentEvent)
            return;

        ignoreItemEvent = true;
        updatePermCheckBoxes();
        ignoreItemEvent = false;
    }
}
