/*
 * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
 *
 * Copyright @ 2015 Atlassian Pty Ltd
 *
 * Licensed 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 net.java.sip.communicator.plugin.desktoputil.wizard;

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.List;

import javax.swing.*;
import javax.swing.border.*;
import javax.swing.table.*;
import javax.swing.event.*;

import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.util.*;
import net.java.sip.communicator.plugin.desktoputil.*;

import org.jitsi.service.neomedia.*;

/**
 * Contains the security settings for SIP media encryption.
 *
 * @author Ingo Bauersachs
 * @author Vincent Lucas
 */
public class SecurityPanel
    extends TransparentPanel
    implements ActionListener,
        TableModelListener
{
    /**
     * Serial version UID.
     */
    private static final long serialVersionUID = 0L;

    private final SecurityAccountRegistration regform;

    private JPanel pnlAdvancedSettings;
    private JCheckBox enableDefaultEncryption;
    private JCheckBox enableSipZrtpAttribute;
    private JComboBox<SavpOption> cboSavpOption;
    private JTable tabCiphers;
    private CipherTableModel cipherModel;
    private JLabel cmdExpandAdvancedSettings;

    /**
     * The TableModel used to configure the encryption protocols preferences.
     */
    private EncryptionConfigurationTableModel encryptionConfigurationTableModel;

    /**
     * JTable with 2 buttons (up and down) which able to enable encryption
     * protocols and to choose their priority order.
     */
    private PriorityTable encryptionProtocolPreferences;

    /**
     * Boolean used to display or not the SAVP options (only useful for SIP, not
     * for XMPP).
     */
    private boolean displaySavpOtions;

    private static class SavpOption
    {
        int option;

        SavpOption(int option)
        {
            this.option = option;
        }

        @Override
        public String toString()
        {
            return UtilActivator.getResources().getI18NString(
                "plugin.sipaccregwizz.SAVP_OPTION_" + option);
        }
    }

    private static class Entry
    {
        String cipher;
        Boolean enabled;

        public Entry(String cipher, boolean enabled)
        {
            this.cipher = cipher;
            this.enabled = enabled;
        }
    }

    private static class CipherTableModel extends AbstractTableModel
    {
        /**
         * Serial version UID.
         */
        private static final long serialVersionUID = 0L;

        private List<Entry> data = new ArrayList<Entry>();

        private final String defaultCiphers =
            UtilActivator.getConfigurationService()
                .getString(SDesControl.SDES_CIPHER_SUITES);

        CipherTableModel(String ciphers)
        {
            loadData(ciphers);
        }

        public void loadData(String ciphers)
        {
            data.clear();

            if(defaultCiphers == null)
                return;

            if(ciphers == null)
                ciphers = defaultCiphers;

            MediaService ms = DesktopUtilActivator.getMediaService();
            SDesControl srtp =
                (SDesControl) ms.createSrtpControl(SrtpControlType.SDES);
            for (String cipher : srtp.getSupportedCryptoSuites())
            {
                data.add(new Entry(cipher, ciphers.contains(cipher)));
            }

            fireTableDataChanged();
        }

        @Override
        public Class<?> getColumnClass(int columnIndex)
        {
            switch(columnIndex)
            {
                case 0:
                    return Boolean.class;
                case 1:
                    return String.class;
            }
            return null;
        }

        public int getRowCount()
        {
            return data.size();
        }

        public int getColumnCount()
        {
            return 2;
        }

        @Override
        public boolean isCellEditable(int rowIndex, int columnIndex)
        {
            return (columnIndex == 0);
        }

        public Object getValueAt(int rowIndex, int columnIndex)
        {
            Entry e = data.get(rowIndex);
            switch(columnIndex)
            {
                case 0:
                    return e.enabled;
                case 1:
                    return e.cipher;
            }
            return null;
        }

        @Override
        public void setValueAt(Object value, int rowIndex, int columnIndex)
        {
            if ((columnIndex == 0) && (value instanceof Boolean))
            {
                Entry e = data.get(rowIndex);
                e.enabled = (Boolean)value;
                fireTableCellUpdated(rowIndex, columnIndex);
            }
        }

        String getEnabledCiphers()
        {
            StringBuilder sb = new StringBuilder();
            for (Entry e : data)
            {
                if(e.enabled)
                {
                    sb.append(e.cipher);
                    sb.append(',');
                }
            }
            if(sb.length() == 0)
            {
                return sb.toString();
            }
            else
            {
                return sb.substring(0, sb.length()-1);
            }
        }
    }

    /**
     * Initiates a panel to configure the security (ZRTP and SDES) for SIP or
     * XMPP protocols.
     *
     * @param regform The registration form of the account to configure.
     * @param displaySavpOptions Boolean used to display or not the SAVP options
     * (only useful for SIP, not for XMPP).
     */
    public SecurityPanel(
            SecurityAccountRegistration regform,
            boolean displaySavpOptions)
    {
        super(new BorderLayout(10, 10));

        this.regform = regform;
        this.displaySavpOtions = displaySavpOptions;
        initComponents();
    }

    private void initComponents()
    {
        setLayout(new BorderLayout());
        final JPanel mainPanel = new TransparentPanel();
        add(mainPanel, BorderLayout.NORTH);

        mainPanel.setLayout(new GridBagLayout());
        GridBagConstraints c = new GridBagConstraints();
        c.gridx = 0;
        c.gridy = 0;
        c.weightx = 1;
        c.anchor = GridBagConstraints.LINE_START;
        c.fill = GridBagConstraints.HORIZONTAL;

        //general encryption option
        enableDefaultEncryption = new SIPCommCheckBox(UtilActivator
            .getResources()
              .getI18NString("plugin.sipaccregwizz.ENABLE_DEFAULT_ENCRYPTION"),
            regform.isDefaultEncryption());
        enableDefaultEncryption.addActionListener(this);
        mainPanel.add(enableDefaultEncryption, c);

        //warning message and button to show advanced options
        JLabel lblWarning = new JLabel();
        lblWarning.setBorder(new EmptyBorder(10, 5, 10, 0));
        lblWarning.setText(UtilActivator.getResources().getI18NString(
            "plugin.sipaccregwizz.SECURITY_WARNING",
            new String[]{
                UtilActivator.getResources().getSettingsString(
                    "service.gui.APPLICATION_NAME")
            }
        ));
        c.gridy++;
        mainPanel.add(lblWarning, c);

        cmdExpandAdvancedSettings = new JLabel();
        cmdExpandAdvancedSettings.setBorder(new EmptyBorder(0, 5, 0, 0));
        cmdExpandAdvancedSettings.setIcon(UtilActivator.getResources()
            .getImage("service.gui.icons.RIGHT_ARROW_ICON"));
        cmdExpandAdvancedSettings.setText(UtilActivator.getResources()
            .getI18NString("plugin.sipaccregwizz.SHOW_ADVANCED"));
        cmdExpandAdvancedSettings.addMouseListener(new MouseAdapter()
        {
            @Override
            public void mouseClicked(MouseEvent e)
            {
                cmdExpandAdvancedSettings.setIcon(
                        UtilActivator.getResources().getImage(
                            pnlAdvancedSettings.isVisible()
                                    ? "service.gui.icons.RIGHT_ARROW_ICON"
                                    : "service.gui.icons.DOWN_ARROW_ICON"));

                pnlAdvancedSettings.setVisible(
                    !pnlAdvancedSettings.isVisible());

                pnlAdvancedSettings.revalidate();
            }
        });
        c.gridy++;
        mainPanel.add(cmdExpandAdvancedSettings, c);

        pnlAdvancedSettings = new TransparentPanel();
        pnlAdvancedSettings.setLayout(new GridBagLayout());
        pnlAdvancedSettings.setVisible(false);
        c.gridy++;
        mainPanel.add(pnlAdvancedSettings, c);


        c = new GridBagConstraints();
        c.gridx = 0;
        c.gridy = 0;
        c.gridwidth = 2;
        c.gridheight = 1;
        c.anchor = GridBagConstraints.LINE_START;
        c.fill = GridBagConstraints.HORIZONTAL;
        pnlAdvancedSettings.add(new JSeparator(), c);

        // Encryption protcol preferences.
        JLabel lblEncryptionProtocolPreferences = new JLabel();
        lblEncryptionProtocolPreferences.setText(UtilActivator.getResources()
            .getI18NString(
                "plugin.sipaccregwizz.ENCRYPTION_PROTOCOL_PREFERENCES"));
        c.gridy++;
        pnlAdvancedSettings.add(lblEncryptionProtocolPreferences, c);

        int nbEncryptionProtocols
                = SecurityAccountRegistration.ENCRYPTION_PROTOCOLS.size();
        String[] encryptions = new String[nbEncryptionProtocols];
        boolean[] selectedEncryptions = new boolean[nbEncryptionProtocols];

        this.encryptionConfigurationTableModel
            = new EncryptionConfigurationTableModel(
                    encryptions,
                    selectedEncryptions);
        loadEncryptionProtocols(new HashMap<String, Integer>(),
            new HashMap<String, Boolean>());
        this.encryptionProtocolPreferences = new PriorityTable(
                    this.encryptionConfigurationTableModel,
                    60);
        this.encryptionConfigurationTableModel.addTableModelListener(this);
        c.gridy++;
        pnlAdvancedSettings.add(this.encryptionProtocolPreferences, c);

        //ZRTP
        JLabel lblZrtpOption = new JLabel();
        lblZrtpOption.setBorder(new EmptyBorder(5, 5, 5, 0));
        lblZrtpOption.setText(UtilActivator.getResources()
            .getI18NString("plugin.sipaccregwizz.ZRTP_OPTION"));
        c.gridx = 0;
        c.gridy++;
        c.gridwidth = 1;
        pnlAdvancedSettings.add(lblZrtpOption, c);
        c.gridx = 1;
        pnlAdvancedSettings.add(new JSeparator(), c);

        enableSipZrtpAttribute = new SIPCommCheckBox(
            UtilActivator.getResources()
            .getI18NString("plugin.sipaccregwizz.ENABLE_SIPZRTP_ATTRIBUTE"),
            regform.isSipZrtpAttribute());
        c.gridx = 0;
        c.gridy++;
        c.gridwidth = 2;
        pnlAdvancedSettings.add(enableSipZrtpAttribute, c);

        //SDES
        JLabel lblSDesOption = new JLabel();
        lblSDesOption.setBorder(new EmptyBorder(5, 5, 5, 0));
        lblSDesOption.setText( UtilActivator.getResources().getI18NString(
            "plugin.sipaccregwizz.SDES_OPTION"));
        c.gridx = 0;
        c.gridy++;
        c.gridwidth = 1;
        pnlAdvancedSettings.add(lblSDesOption, c);
        c.gridx = 1;
        pnlAdvancedSettings.add(new JSeparator(), c);

        JLabel lblCipherInfo = new JLabel();
        lblCipherInfo.setText(UtilActivator.getResources()
            .getI18NString("plugin.sipaccregwizz.CIPHER_SUITES"));
        c.gridx = 0;
        c.gridy++;
        c.gridwidth = 2;
        pnlAdvancedSettings.add(lblCipherInfo, c);

        cipherModel = new CipherTableModel(regform.getSDesCipherSuites());
        tabCiphers = new JTable(cipherModel);
        tabCiphers.setShowGrid(false);
        tabCiphers.setTableHeader(null);
        TableColumnModel tableColumnModel = tabCiphers.getColumnModel();
        TableColumn tableColumn = tableColumnModel.getColumn(0);
        tableColumn.setMaxWidth(tableColumn.getMinWidth());
        JScrollPane scrollPane = new JScrollPane(tabCiphers);
        scrollPane.setPreferredSize(new Dimension(tabCiphers.getWidth(), 100));
        c.gridy++;
        pnlAdvancedSettings.add(scrollPane, c);

        //SAVP selection
        c.gridx = 0;
        c.gridwidth = 1;
        JLabel lblSavpOption = new JLabel();
        lblSavpOption.setBorder(new EmptyBorder(5, 5, 5, 0));
        lblSavpOption.setText( UtilActivator.getResources().getI18NString(
            "plugin.sipaccregwizz.SAVP_OPTION"));
        if(this.displaySavpOtions)
        {
            c.gridy++;
            pnlAdvancedSettings.add(lblSavpOption, c);
        }
        c.gridx = 1;
        if(this.displaySavpOtions)
        {
            pnlAdvancedSettings.add(new JSeparator(), c);
        }

        cboSavpOption = new JComboBox<SavpOption>(new SavpOption[]{
            new SavpOption(0),
            new SavpOption(1),
            new SavpOption(2)
        });
        c.gridx = 0;
        c.gridwidth = 2;
        c.insets = new Insets(0, 20, 0, 0);
        if(this.displaySavpOtions)
        {
            c.gridy++;
            pnlAdvancedSettings.add(cboSavpOption, c);
        }
    }

    /**
     * Saves the user input when the "Next" wizard buttons is clicked.
     *
     * @param registration the SIPAccountRegistration
     * @return
     */
    public boolean commitPanel(SecurityAccountRegistration registration)
    {
        registration.setDefaultEncryption(enableDefaultEncryption.isSelected());
        registration.setEncryptionProtocols(
                encryptionConfigurationTableModel.getEncryptionProtocols());
        registration.setEncryptionProtocolStatus(
                encryptionConfigurationTableModel
                    .getEncryptionProtocolStatus());
        registration.setSipZrtpAttribute(enableSipZrtpAttribute.isSelected());
        registration.setSavpOption(((SavpOption) cboSavpOption
            .getSelectedItem()).option);
        registration.setSDesCipherSuites(cipherModel.getEnabledCiphers());

        return true;
    }

    /**
     * Loads the account with the given identifier.
     * @param securityAccReg the account identifier
     */
    public void loadAccount(SecurityAccountRegistration securityAccReg)
    {
        enableDefaultEncryption.setSelected(
                securityAccReg.isDefaultEncryption());

        Map<String, Integer> encryptionProtocols
                = securityAccReg.getEncryptionProtocols();
        Map<String, Boolean> encryptionProtocolStatus
                = securityAccReg.getEncryptionProtocolStatus();

        this.loadEncryptionProtocols(
                encryptionProtocols,
                encryptionProtocolStatus);

        enableSipZrtpAttribute.setSelected(securityAccReg.isSipZrtpAttribute());

        cboSavpOption.setSelectedIndex(securityAccReg.getSavpOption());

        cipherModel.loadData(securityAccReg.getSDesCipherSuites());

        loadStates();
    }

    public void actionPerformed(ActionEvent e)
    {
        if(e.getSource() == enableDefaultEncryption)
        {
            loadStates();
        }
        else if(e.getSource() == cmdExpandAdvancedSettings)
        {
            pnlAdvancedSettings.setVisible(!pnlAdvancedSettings.isVisible());
        }
    }

    public void tableChanged(TableModelEvent e)
    {
        if(e.getSource() == this.encryptionConfigurationTableModel)
        {
            loadStates();
        }
    }

    private void loadStates()
    {
        boolean b = enableDefaultEncryption.isSelected();
        cboSavpOption.setEnabled(b);
        this.encryptionProtocolPreferences.setEnabled(b);
        enableSipZrtpAttribute.setEnabled(
                b
                && this.encryptionConfigurationTableModel
                    .isEnabledLabel("ZRTP"));
        tabCiphers.setEnabled(
                b
                && this.encryptionConfigurationTableModel
                    .isEnabledLabel("SDES"));
    }

    /**
     * Loads the list of enabled and disabled encryption protocols with their
     * priority.
     *
     * @param enabledEncryptionProtocols The list of enabled encryption protocol
     * available for this account.
     * @param disabledEncryptionProtocols The list of disabled encryption protocol
     * available for this account.
     */
    private void loadEncryptionProtocols(
            Map<String, Integer> encryptionProtocols,
            Map<String, Boolean> encryptionProtocolStatus)
    {
        Object[] result = SecurityAccountRegistration
                .loadEncryptionProtocols(
                        encryptionProtocols,
                        encryptionProtocolStatus);

        String[] encryptions = (String[]) result[0];
        boolean[] selectedEncryptions = (boolean[]) result[1];

        this.encryptionConfigurationTableModel.init(
                encryptions,
                selectedEncryptions);
    }
}
