/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to you 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.apache.jmeter.visualizers;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.tree.DefaultMutableTreeNode;

import org.apache.commons.lang3.StringUtils;
import org.apache.jmeter.gui.Searchable;
import org.apache.jmeter.gui.action.KeyStrokes;
import org.apache.jmeter.gui.action.RawTextSearcher;
import org.apache.jmeter.gui.action.RegexpSearcher;
import org.apache.jmeter.gui.action.Searcher;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jmeter.visualizers.utils.Colors;
import org.apache.jorphan.gui.JFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Panel used by {@link ViewResultsFullVisualizer} to search for data within the Tree
 * @since 3.0
 */
public class SearchTreePanel extends JPanel implements ActionListener {

    private static final long serialVersionUID = 1L;

    private static final Logger log = LoggerFactory.getLogger(SearchTreePanel.class);

    private static final String SEARCH_TEXT_COMMAND = "search_text"; // $NON-NLS-1$

    private JButton searchButton;

    private JTextField searchTF;

    private JCheckBox isRegexpCB;

    private JCheckBox isCaseSensitiveCB;

    private JButton resetButton;

    private DefaultMutableTreeNode defaultMutableTreeNode;

    public SearchTreePanel(DefaultMutableTreeNode defaultMutableTreeNode) {
        super();
        init();
        this.defaultMutableTreeNode = defaultMutableTreeNode;
    }

    /**
     * @deprecated only for use by test code
     */
    @Deprecated
    public SearchTreePanel() {
    }

    private class EnterAction extends AbstractAction {
        private static final long serialVersionUID = 2L;
        @Override
        public void actionPerformed(ActionEvent ev) {
            boolean found = doSearch();
            if(found) {
                searchTF.setBackground(Color.WHITE);
                searchTF.setForeground(Color.BLACK);
            }
            else {
                searchTF.setBackground(Colors.LIGHT_RED);
                searchTF.setForeground(Color.WHITE);
            }
        }
    }

    private void init() { // WARNING: called from ctor so must not be overridden (i.e. must be private or final)
        setLayout(new BorderLayout(10,10));

        searchTF = new JTextField(20); //$NON-NLS-1$
        JFactory.small(searchTF);

        InputMap im = searchTF
                .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
        im.put(KeyStrokes.ENTER, SEARCH_TEXT_COMMAND);
        ActionMap am = searchTF.getActionMap();
        am.put(SEARCH_TEXT_COMMAND, new EnterAction());

        isRegexpCB = new JCheckBox(JMeterUtils.getResString("search_text_chkbox_regexp"), false); //$NON-NLS-1$
        isCaseSensitiveCB = new JCheckBox(JMeterUtils.getResString("search_text_chkbox_case"), false); //$NON-NLS-1$

        JFactory.small(isRegexpCB);
        JFactory.small(isCaseSensitiveCB);

        searchButton = new JButton(JMeterUtils.getResString("search")); //$NON-NLS-1$
        searchButton.addActionListener(this);
        resetButton = new JButton(JMeterUtils.getResString("reset")); //$NON-NLS-1$
        resetButton.addActionListener(this);

        JPanel searchPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));

        JLabel searchLabel = new JLabel(JMeterUtils.getResString("search_text_field"));
        JFactory.small(searchLabel);
        searchPanel.add(searchLabel);
        searchPanel.add(searchTF);
        searchPanel.add(isCaseSensitiveCB);
        searchPanel.add(isRegexpCB);
        searchPanel.add(searchButton);
        searchPanel.add(resetButton);
        add(searchPanel);
    }

    /**
     * Do search
     * @param e {@link ActionEvent}
     */
    @Override
    public void actionPerformed(ActionEvent e) {
        if(e.getSource() == searchButton) {
            doSearch();
        } else if (e.getSource() == resetButton) {
            doResetSearch((SearchableTreeNode)defaultMutableTreeNode);
        }
    }

    /**
     * @param searchableTreeNode
     */
    private static void doResetSearch(SearchableTreeNode searchableTreeNode) {
        searchableTreeNode.reset();
        searchableTreeNode.updateState();
        for (int i = 0; i < searchableTreeNode.getChildCount(); i++) {
            doResetSearch((SearchableTreeNode)searchableTreeNode.getChildAt(i));
        }
    }


    /**
     * return true if a match occurred
     */
    private boolean doSearch() {
        String wordToSearch = searchTF.getText();
        if (StringUtils.isEmpty(wordToSearch)) {
            return false;
        }
        Searcher searcher = isRegexpCB.isSelected() ?
            new RegexpSearcher(isCaseSensitiveCB.isSelected(), searchTF.getText()) :
            new RawTextSearcher(isCaseSensitiveCB.isSelected(), searchTF.getText());
        return searchInNode(searcher, (SearchableTreeNode)defaultMutableTreeNode);
    }

    /**
     * @param searcher
     * @param node
     */
    private static boolean searchInNode(Searcher searcher, SearchableTreeNode node) {
        node.reset();
        Object userObject = node.getUserObject();

        try {
            Searchable searchable;
            if(userObject instanceof Searchable) {
                searchable = (Searchable) userObject;
            } else {
                return false;
            }
            if(searcher.search(searchable.getSearchableTokens())) {
                node.setNodeHasMatched(true);
            }
            boolean foundInChildren = false;
            for (int i = 0; i < node.getChildCount(); i++) {
                searchInNode(searcher, (SearchableTreeNode)node.getChildAt(i));
                foundInChildren =
                        searchInNode(searcher, (SearchableTreeNode)node.getChildAt(i))
                        || foundInChildren; // Must be the last in condition
            }
            if(!node.isNodeHasMatched()) {
                node.setChildrenNodesHaveMatched(foundInChildren);
            }
            node.updateState();
            return node.isNodeHasMatched() || node.isChildrenNodesHaveMatched();
        } catch (Exception e) {
            log.error("Error extracting data from tree node using searcher:{}", searcher, e);
            return false;
        }
    }
}
