/**
 * Copyright (C) 2004-2011 Jive Software. All rights reserved.
 *
 * 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 org.jivesoftware.sparkimpl.search.users;

import org.jivesoftware.Spark;
import org.jivesoftware.resource.Res;
import org.jivesoftware.resource.SparkRes;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.search.UserSearchManager;
import org.jivesoftware.smackx.xdata.packet.DataForm;
import org.jivesoftware.spark.SparkManager;
import org.jivesoftware.spark.component.TitlePanel;
import org.jivesoftware.spark.ui.DataFormUI;
import org.jivesoftware.spark.util.ModelUtil;
import org.jivesoftware.spark.util.ResourceUtils;
import org.jivesoftware.spark.util.SwingWorker;
import org.jivesoftware.spark.util.log.Log;
import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;

import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.UIManager;

import java.awt.CardLayout;
import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import java.io.File; 
import java.io.FileInputStream; 
import java.io.FileOutputStream;
import java.io.IOException;


/**
 * UserSearchForm is used to do explicit searching of users using the JEP 55 Search Service.
 */
public class UserSearchForm extends JPanel {
    private JComboBox<String> servicesBox;
    private final UserSearchManager searchManager;

    private final Collection<DomainBareJid> searchServices;

    private final CardLayout cardLayout = new CardLayout();
    private final JPanel cardPanel = new JPanel();

    private TitlePanel titlePanel;

    private final Map<String,SearchForm> serviceMap = new HashMap<>();

    private static final File searchSettingsFile = new File(Spark.getSparkUserHome(), "search.properties");
    
    /**
     * Initializes the UserSearchForm with all available search services.
     *
     * @param searchServices a Collection of all search services found.
     */
    public UserSearchForm(Collection<DomainBareJid> searchServices) {
        setLayout(new GridBagLayout());
        cardPanel.setLayout(cardLayout);
        this.searchServices = searchServices;
        searchManager = UserSearchManager.getInstanceFor(SparkManager.getConnection());
        addSearchServices();
        showService(getSearchService());
    }

    private void addSearchServices() {
        // Populate with Search Services
        servicesBox = new JComboBox<>();
        for (CharSequence searchService : searchServices) {
            String service = searchService.toString();
            servicesBox.addItem(service);
        }
        Properties searchSettings = loadCustomSearchServices();
        if (servicesBox.getItemCount() > 0) {
            servicesBox.setSelectedIndex(0);
        }

        titlePanel = new TitlePanel("", "", SparkRes.getImageIcon(SparkRes.Icon.BLANK_IMAGE), true);
        add(titlePanel, new GridBagConstraints(0, 0, 3, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));

        // Add Search Service ComboBox
        final JLabel serviceLabel = new JLabel("");
        ResourceUtils.resLabel(serviceLabel, servicesBox, Res.getString("label.search.service") + ":");
        add(serviceLabel, new GridBagConstraints(0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0));
        add(servicesBox, new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 150, 0));

        final JButton addService = new JButton();
        ResourceUtils.resButton(addService, Res.getString("button.add.service"));
        add(addService, new GridBagConstraints(2, 1, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0));
        addService.addActionListener( actionEvent -> {
            final String serviceName = JOptionPane.showInputDialog(getRootPane(), Res.getString("message.name.of.search.service.question"), Res.getString("title.add.search.service"), JOptionPane.QUESTION_MESSAGE);
            if (ModelUtil.hasLength(serviceName)) {

                SwingWorker findServiceThread = new SwingWorker() {
                    DataForm newForm;

                    @Override
					public Object construct() {
                        try {
                            DomainBareJid serviceJid = JidCreate.domainBareFrom(serviceName);
                            newForm = searchManager.getSearchForm(serviceJid).getDataForm();
                            return newForm;
                        }
                        catch (XMPPException | SmackException | XmppStringprepException | InterruptedException e) {
                           Log.error("Unable to get search form", e);
                           return null;
                        }
                    }

                    @Override
					public void finished() {
                        if (newForm == null) {
                            JOptionPane.showMessageDialog(getGUI(), Res.getString("message.search.service.not.available"), Res.getString("title.notification"), JOptionPane.ERROR_MESSAGE);
                        }
                        else {
                            servicesBox.addItem(serviceName);
                            servicesBox.setSelectedItem(serviceName);

                            saveCustomSearchService(searchSettings, serviceName);
                        }
                    }
                };
                findServiceThread.start();
            }
        } );

        servicesBox.addActionListener( actionEvent -> {
            SwingWorker worker = new SwingWorker() {
                @Override
				public Object construct() {
                    try {
                        Thread.sleep(50);
                    }
                    catch (Exception e) {
                        Log.error("Problem sleeping thread.", e);
                    }
                    return "ok";
                }

                @Override
				public void finished() {
                    showService(getSearchService());
                }
            };
            worker.start();
        } );

        add(cardPanel, new GridBagConstraints(0, 3, 3, 1, 1.0, 1.0, GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 0, 0));
    }

    /**
     * Displays the specified search service.
     */
    public void showService(String service) {
        if (!serviceMap.containsKey(service)) {
            // Create new Form
            SearchForm searchForm = new SearchForm(service);
            cardPanel.add(searchForm, service);
            serviceMap.put(service, searchForm);
        }
        cardLayout.show(cardPanel, service);

        SearchForm searchForm = serviceMap.get(service);
        DataForm form = searchForm.getSearchForm();
        String description = String.join("\n", form.getInstructions());

        titlePanel.setTitle(Res.getString("title.person.search"));
        titlePanel.setDescription(description);
    }

    /**
     * Returns the selected search service.
     */
    public String getSearchService() {
        return (String)servicesBox.getSelectedItem();
    }

    /**
     * Return the QuestionForm retrieved by the search service.
     */
    public DataFormUI getQuestionForm() {
        SearchForm searchForm = serviceMap.get(getSearchService());
        return searchForm.getQuestionForm();
    }

    /**
     * Performs a search on the specified search service.
     */
    public void performSearch() {
        SearchForm searchForm = serviceMap.get(getSearchService());
        searchForm.performSearch();
    }

    /**
     * Returns the UI that represent the UserSearchForm
     */
    public Component getGUI() {
        return this;
    }

    private static void saveCustomSearchService(Properties props, String serviceName) {
        int numbProp = 0;
        while (true) {
            String nextProp = "search" + numbProp;
            String testSearch = props.getProperty(nextProp);
            if (testSearch != null)
                numbProp++;
            else {
                Log.warning("Search-Service: " + nextProp + " : " + serviceName + " added");
                props.setProperty(nextProp, serviceName);
                break;
            }
        }
        try {
            props.store(new FileOutputStream(searchSettingsFile), null);
        } catch (IOException e) {
            Log.error(e);
        }
    }

    private Properties loadCustomSearchServices() {
        // Load the property file and add the search services that are read
        Properties props = new Properties();
        if (searchSettingsFile.exists()) {
            Log.warning("Search-service Properties-file does exist= " + searchSettingsFile.getPath());
            try {
                int numbProp = 0;
                props.load(new FileInputStream(searchSettingsFile));
                while (true) {
                    String nextProp = "search" + numbProp;
                    String testSearch = props.getProperty(nextProp);
                    if (testSearch != null) {
                        Log.warning("Search-Info: SearchService-" + numbProp + " from properties-file is " + nextProp + " : " + testSearch);
                        servicesBox.addItem(testSearch);
                        numbProp++;
                    } else {
                       break;
                    }
                }
            } catch (IOException ioe) {
                Log.error(ioe);
            }
        }
        return props;
    }
}
