/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * 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 com.android.ddmuilib.logcat;

import com.android.ddmlib.Log.LogLevel;

import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.TitleAreaDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

/**
 * Dialog used to create or edit settings for a logcat filter.
 */
public final class LogCatFilterSettingsDialog extends TitleAreaDialog {
    private static final String TITLE = "Logcat Message Filter Settings";
    private static final String DEFAULT_MESSAGE =
            "Filter logcat messages by the source's tag, pid or minimum log level.\n"
            + "Empty fields will match all messages.";

    private String mFilterName;
    private String mTag;
    private String mPID;
    private String mLogLevel;

    private Text mFilterNameText;
    private Text mTagFilterText;
    private Text mPIDFilterText;
    private Combo mLogLevelCombo;
    private Button mOkButton;

    /**
     * Construct the filter settings dialog with default values for all fields.
     * @param parentShell .
     */
    public LogCatFilterSettingsDialog(Shell parentShell) {
        super(parentShell);
        setDefaults("", "", "", LogLevel.VERBOSE);
    }

    /**
     * Set the default values to show when the dialog is opened.
     * @param filterName name for the filter.
     * @param tag value for filter by tag
     * @param pid value for filter by pid
     * @param level value for filter by log level
     */
    public void setDefaults(String filterName, String tag, String pid, LogLevel level) {
        mFilterName = filterName;
        mTag = tag;
        mPID = pid;
        mLogLevel = level.getStringValue();
    }

    @Override
    protected Control createDialogArea(Composite shell) {
        setTitle(TITLE);
        setMessage(DEFAULT_MESSAGE);

        Composite parent = (Composite) super.createDialogArea(shell);
        Composite c = new Composite(parent, SWT.BORDER);
        c.setLayout(new GridLayout(2, false));
        c.setLayoutData(new GridData(GridData.FILL_BOTH));

        createLabel(c, "Filter Name:");
        mFilterNameText = new Text(c, SWT.BORDER);
        mFilterNameText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        mFilterNameText.setText(mFilterName);

        createSeparator(c);

        createLabel(c, "by Log Tag:");
        mTagFilterText = new Text(c, SWT.BORDER);
        mTagFilterText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        mTagFilterText.setText(mTag);

        createLabel(c, "by PID:");
        mPIDFilterText = new Text(c, SWT.BORDER);
        mPIDFilterText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        mPIDFilterText.setText(mPID);

        createLabel(c, "by Log Level:");
        mLogLevelCombo = new Combo(c, SWT.READ_ONLY | SWT.DROP_DOWN);
        mLogLevelCombo.setItems(getLogLevels().toArray(new String[0]));
        mLogLevelCombo.select(getLogLevels().indexOf(mLogLevel));

        /* call validateDialog() whenever user modifies any text field */
        ModifyListener m = new ModifyListener() {
            public void modifyText(ModifyEvent arg0) {
                DialogStatus status = validateDialog();
                mOkButton.setEnabled(status.valid);
                setErrorMessage(status.message);
            }
        };
        mFilterNameText.addModifyListener(m);
        mTagFilterText.addModifyListener(m);
        mPIDFilterText.addModifyListener(m);

        return c;
    }


    @Override
    protected void createButtonsForButtonBar(Composite parent) {
        super.createButtonsForButtonBar(parent);

        mOkButton = getButton(IDialogConstants.OK_ID);

        DialogStatus status = validateDialog();
        mOkButton.setEnabled(status.valid);
    }

    /**
     * A tuple that specifies whether the current state of the inputs
     * on the dialog is valid or not. If it is not valid, the message
     * field stores the reason why it isn't.
     */
    private final class DialogStatus {
        final boolean valid;
        final String message;

        private DialogStatus(boolean isValid, String errMessage) {
            valid = isValid;
            message = errMessage;
        }
    }

    private DialogStatus validateDialog() {
        /* check that there is some name for the filter */
        if (mFilterNameText.getText().trim().equals("")) {
            return new DialogStatus(false,
                    "Please provide a name for this filter.");
        }

        /* if a pid is provided, it should be a +ve integer */
        String pidText = mPIDFilterText.getText().trim();
        if (!pidText.equals("")) {
            int pid = 0;
            try {
                pid = Integer.parseInt(pidText);
            } catch (NumberFormatException e) {
                return new DialogStatus(false,
                        "PID should be a positive integer.");
            }

            if (pid < 0) {
                return new DialogStatus(false,
                        "PID should be a positive integer.");
            }
        }

        /* tag field must use a valid regex pattern */
        String tagText = mTagFilterText.getText().trim();
        if (!tagText.equals("")) {
            try {
                Pattern.compile(tagText);
            } catch (PatternSyntaxException e) {
                return new DialogStatus(false,
                        "Invalid regex used in tag field: " + e.getMessage());
            }
        }

        return new DialogStatus(true, null);
    }

    private void createSeparator(Composite c) {
        Label l = new Label(c, SWT.SEPARATOR | SWT.HORIZONTAL);
        GridData gd = new GridData(GridData.FILL_HORIZONTAL);
        gd.horizontalSpan = 2;
        l.setLayoutData(gd);
    }

    private void createLabel(Composite c, String text) {
        Label l = new Label(c, SWT.NONE);
        l.setText(text);
        GridData gd = new GridData();
        gd.horizontalAlignment = SWT.RIGHT;
        l.setLayoutData(gd);
    }

    @Override
    protected void okPressed() {
        /* save values from the widgets before the shell is closed. */
        mFilterName = mFilterNameText.getText();
        mTag = mTagFilterText.getText();
        mLogLevel = mLogLevelCombo.getText();

        String pidText = mPIDFilterText.getText().trim();
        if (!pidText.equals("")) {
            mPID = pidText;
        }

        super.okPressed();
    }

    /**
     * Obtain the name for this filter.
     * @return user provided filter name, maybe empty.
     */
    public String getFilterName() {
        return mFilterName;
    }

    /**
     * Obtain the tag regex to filter by.
     * @return user provided tag regex, maybe empty.
     */
    public String getTag() {
        return mTag;
    }

    /**
     * Obtain user provided PID to filter by.
     * @return user provided pid, maybe empty.
     */
    public String getPID() {
        return mPID;
    }

    /**
     * Obtain log level to filter by.
     * @return log level string.
     */
    public String getLogLevel() {
        return mLogLevel;
    }

    /**
     * Obtain the string representation of all supported log levels.
     * @return an array of strings, each representing a certain log level.
     */
    public static List<String> getLogLevels() {
        List<String> logLevels = new ArrayList<String>();

        for (LogLevel l : LogLevel.values()) {
            logLevels.add(l.getStringValue());
        }

        return logLevels;
    }
}
