/*
 * Copyright 2010-2015 JetBrains s.r.o.
 *
 * 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.jetbrains.kotlin.idea.debugger.breakpoints;

import com.intellij.debugger.InstanceFilter;
import com.intellij.debugger.ui.breakpoints.EditClassFiltersDialog;
import com.intellij.debugger.ui.breakpoints.EditInstanceFiltersDialog;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiClass;
import com.intellij.ui.FieldPanel;
import com.intellij.ui.MultiLineTooltipUI;
import com.intellij.ui.classFilter.ClassFilter;
import com.intellij.xdebugger.XSourcePosition;
import com.intellij.xdebugger.breakpoints.XBreakpoint;
import com.intellij.xdebugger.breakpoints.ui.XBreakpointCustomPropertiesPanel;
import com.intellij.xdebugger.impl.breakpoints.XBreakpointBase;
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.java.debugger.breakpoints.properties.JavaBreakpointProperties;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class KotlinBreakpointFiltersPanel<T extends KotlinPropertyBreakpointProperties, B extends XBreakpoint<T>> extends XBreakpointCustomPropertiesPanel<B> {
  private JPanel myConditionsPanel;
  private JPanel myInstanceFiltersPanel;
  private JCheckBox myInstanceFiltersCheckBox;
  private JPanel myInstanceFiltersFieldPanel;
  private JPanel myClassFiltersPanel;
  private JCheckBox myClassFiltersCheckBox;
  private JPanel myClassFiltersFieldPanel;
  private JPanel myPassCountPanel;
  private JCheckBox myPassCountCheckbox;
  private JTextField myPassCountField;

  private final FieldPanel myInstanceFiltersField;
  private final FieldPanel myClassFiltersField;

  private ClassFilter[] myClassFilters = ClassFilter.EMPTY_ARRAY;
  private ClassFilter[] myClassExclusionFilters = ClassFilter.EMPTY_ARRAY;
  private InstanceFilter[] myInstanceFilters = InstanceFilter.EMPTY_ARRAY;
  protected final Project myProject;

  private PsiClass myBreakpointPsiClass;

  public KotlinBreakpointFiltersPanel(Project project) {
    myProject = project;
    myInstanceFiltersField = new FieldPanel(new MyTextField(), "", null,
                                            new ActionListener() {
                                              @Override
                                              public void actionPerformed(ActionEvent e) {
                                                reloadInstanceFilters();
                                                EditInstanceFiltersDialog _dialog = new EditInstanceFiltersDialog(myProject);
                                                _dialog.setFilters(myInstanceFilters);
                                                _dialog.show();
                                                if (_dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) {
                                                  myInstanceFilters = _dialog.getFilters();
                                                  updateInstanceFilterEditor(true);
                                                }
                                              }
                                            },
                                            null
    );

    myClassFiltersField = new FieldPanel(new MyTextField(), "", null,
                                         new ActionListener() {
                                           @Override
                                           public void actionPerformed(ActionEvent e) {
                                             reloadClassFilters();

                                             com.intellij.ide.util.ClassFilter classFilter = createClassConditionFilter();

                                             EditClassFiltersDialog _dialog = new EditClassFiltersDialog(myProject, classFilter);
                                             _dialog.setFilters(myClassFilters, myClassExclusionFilters);
                                             _dialog.show();
                                             if (_dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) {
                                               myClassFilters = _dialog.getFilters();
                                               myClassExclusionFilters = _dialog.getExclusionFilters();
                                               updateClassFilterEditor(true);
                                             }
                                           }
                                         },
                                         null
    );

    ActionListener updateListener = new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        updateCheckboxes();
      }
    };

    myPassCountCheckbox.addActionListener(updateListener);
    myInstanceFiltersCheckBox.addActionListener(updateListener);
    myClassFiltersCheckBox.addActionListener(updateListener);

    ToolTipManager.sharedInstance().registerComponent(myClassFiltersField.getTextField());
    ToolTipManager.sharedInstance().registerComponent(myInstanceFiltersField.getTextField());

    insert(myInstanceFiltersFieldPanel, myInstanceFiltersField);
    insert(myClassFiltersFieldPanel, myClassFiltersField);

    DebuggerUIUtil.focusEditorOnCheck(myPassCountCheckbox, myPassCountField);
    DebuggerUIUtil.focusEditorOnCheck(myInstanceFiltersCheckBox, myInstanceFiltersField.getTextField());
    DebuggerUIUtil.focusEditorOnCheck(myClassFiltersCheckBox, myClassFiltersField.getTextField());
  }

  @NotNull
  @Override
  public JComponent getComponent() {
    return myConditionsPanel;
  }

  @Override
  public boolean isVisibleOnPopup(@NotNull B breakpoint) {
    JavaBreakpointProperties properties = breakpoint.getProperties();
    if (properties != null) {
      return properties.isCOUNT_FILTER_ENABLED() || properties.isCLASS_FILTERS_ENABLED() || properties.isINSTANCE_FILTERS_ENABLED();
    }
    return false;
  }

  @Override
  public void saveTo(@NotNull B breakpoint) {
    JavaBreakpointProperties properties = breakpoint.getProperties();
    if (properties == null) {
      return;
    }

    boolean changed = false;
    try {
      String text = myPassCountField.getText().trim();
      int filter = !text.isEmpty() ? Integer.parseInt(text) : 0;
      if (filter < 0) filter = 0;
      changed = properties.setCOUNT_FILTER(filter);
    }
    catch (Exception ignored) {
    }

    changed = properties.setCOUNT_FILTER_ENABLED(properties.getCOUNT_FILTER() > 0 && myPassCountCheckbox.isSelected()) || changed;
    reloadInstanceFilters();
    reloadClassFilters();
    updateInstanceFilterEditor(true);
    updateClassFilterEditor(true);

    changed = properties.setINSTANCE_FILTERS_ENABLED(myInstanceFiltersField.getText().length() > 0 && myInstanceFiltersCheckBox.isSelected()) || changed;
    changed = properties.setCLASS_FILTERS_ENABLED(myClassFiltersField.getText().length() > 0 && myClassFiltersCheckBox.isSelected()) || changed;
    changed = properties.setClassFilters(myClassFilters) || changed;
    changed = properties.setClassExclusionFilters(myClassExclusionFilters) || changed;
    changed = properties.setInstanceFilters(myInstanceFilters) || changed;
    if (changed) {
      ((XBreakpointBase)breakpoint).fireBreakpointChanged();
    }
  }

  private static void insert(JPanel panel, JComponent component) {
    panel.setLayout(new BorderLayout());
    panel.add(component, BorderLayout.CENTER);
  }

  @Override
  public void loadFrom(@NotNull B breakpoint) {
    JavaBreakpointProperties properties = breakpoint.getProperties();
    if (properties != null) {
      if (properties.getCOUNT_FILTER() > 0) {
        myPassCountField.setText(Integer.toString(properties.getCOUNT_FILTER()));
      }
      else {
        myPassCountField.setText("");
      }

      myPassCountCheckbox.setSelected(properties.isCOUNT_FILTER_ENABLED());

      myInstanceFiltersCheckBox.setSelected(properties.isINSTANCE_FILTERS_ENABLED());
      myInstanceFiltersField.setEnabled(properties.isINSTANCE_FILTERS_ENABLED());
      myInstanceFiltersField.getTextField().setEditable(properties.isINSTANCE_FILTERS_ENABLED());
      myInstanceFilters = properties.getInstanceFilters();
      updateInstanceFilterEditor(true);

      myClassFiltersCheckBox.setSelected(properties.isCLASS_FILTERS_ENABLED());
      myClassFiltersField.setEnabled(properties.isCLASS_FILTERS_ENABLED());
      myClassFiltersField.getTextField().setEditable(properties.isCLASS_FILTERS_ENABLED());
      myClassFilters = properties.getClassFilters();
      myClassExclusionFilters = properties.getClassExclusionFilters();
      updateClassFilterEditor(true);

      XSourcePosition position = breakpoint.getSourcePosition();
      // TODO: need to calculate psi class
      //myBreakpointPsiClass = breakpoint.getPsiClass();
    }
    updateCheckboxes();
  }

  private void updateInstanceFilterEditor(boolean updateText) {
    List<String> filters = new ArrayList<String>();
    for (InstanceFilter instanceFilter : myInstanceFilters) {
      if (instanceFilter.isEnabled()) {
        filters.add(Long.toString(instanceFilter.getId()));
      }
    }
    if (updateText) {
      myInstanceFiltersField.setText(StringUtil.join(filters, " "));
    }

    String tipText = concatWithEx(filters, " ", (int)Math.sqrt(myInstanceFilters.length) + 1, "\n");
    myInstanceFiltersField.getTextField().setToolTipText(tipText);
  }

  private class MyTextField extends JTextField {
    public MyTextField() {
    }

    @Override
    public String getToolTipText(MouseEvent event) {
      reloadClassFilters();
      updateClassFilterEditor(false);
      reloadInstanceFilters();
      updateInstanceFilterEditor(false);
      String toolTipText = super.getToolTipText(event);
      return getToolTipText().length() == 0 ? null : toolTipText;
    }

    @Override
    public JToolTip createToolTip() {
      JToolTip toolTip = new JToolTip(){{
        setUI(new MultiLineTooltipUI());
      }};
      toolTip.setComponent(this);
      return toolTip;
    }
  }

  private void reloadClassFilters() {
    String filtersText = myClassFiltersField.getText();

    ArrayList<ClassFilter> classFilters     = new ArrayList<ClassFilter>();
    ArrayList<ClassFilter> exclusionFilters = new ArrayList<ClassFilter>();
    int startFilter = -1;
    for(int i = 0; i <= filtersText.length(); i++) {
      if(i < filtersText.length() && !Character.isWhitespace(filtersText.charAt(i))){
        if(startFilter == -1) {
          startFilter = i;
        }
      }
      else {
        if(startFilter >=0) {
          if(filtersText.charAt(startFilter) == '-') {
            exclusionFilters.add(new ClassFilter(filtersText.substring(startFilter + 1, i)));
          }
          else {
            classFilters.add(new ClassFilter(filtersText.substring(startFilter, i)));
          }
          startFilter = -1;
        }
      }
    }
    for (ClassFilter classFilter : myClassFilters) {
      if (!classFilter.isEnabled()) {
        classFilters.add(classFilter);
      }
    }
    for (ClassFilter classFilter : myClassExclusionFilters) {
      if (!classFilter.isEnabled()) {
        exclusionFilters.add(classFilter);
      }
    }
    myClassFilters          = classFilters    .toArray(new ClassFilter[classFilters    .size()]);
    myClassExclusionFilters = exclusionFilters.toArray(new ClassFilter[exclusionFilters.size()]);
  }

  private void reloadInstanceFilters() {
    String filtersText = myInstanceFiltersField.getText();

    ArrayList<InstanceFilter> idxs = new ArrayList<InstanceFilter>();
    int startNumber = -1;
    for(int i = 0; i <= filtersText.length(); i++) {
      if(i < filtersText.length() && Character.isDigit(filtersText.charAt(i))) {
        if(startNumber == -1) {
          startNumber = i;
        }
      }
      else {
        if(startNumber >=0) {
          idxs.add(InstanceFilter.create(filtersText.substring(startNumber, i)));
          startNumber = -1;
        }
      }
    }
    for (InstanceFilter instanceFilter : myInstanceFilters) {
      if (!instanceFilter.isEnabled()) {
        idxs.add(instanceFilter);
      }
    }
    myInstanceFilters = idxs.toArray(new InstanceFilter[idxs.size()]);
  }

  private void updateClassFilterEditor(boolean updateText) {
    List<String> filters = new ArrayList<String>();
    for (ClassFilter classFilter : myClassFilters) {
      if (classFilter.isEnabled()) {
        filters.add(classFilter.getPattern());
      }
    }
    List<String> excludeFilters = new ArrayList<String>();
    for (ClassFilter classFilter : myClassExclusionFilters) {
      if (classFilter.isEnabled()) {
        excludeFilters.add("-" + classFilter.getPattern());
      }
    }
    if (updateText) {
      String editorText = StringUtil.join(filters, " ");
      if(!filters.isEmpty()) {
        editorText += " ";
      }
      editorText += StringUtil.join(excludeFilters, " ");
      myClassFiltersField.setText(editorText);
    }

    int width = (int)Math.sqrt(myClassExclusionFilters.length + myClassFilters.length) + 1;
    String tipText = concatWithEx(filters, " ", width, "\n");
    if(!filters.isEmpty()) {
      tipText += "\n";
    }
    tipText += concatWithEx(excludeFilters, " ", width, "\n");
    myClassFiltersField.getTextField().setToolTipText(tipText);
  }

  private static String concatWithEx(List<String> s, String concator, int N, String NthConcator) {
    String result = "";
    int i = 1;
    for (Iterator iterator = s.iterator(); iterator.hasNext(); i++) {
      String str = (String) iterator.next();
      result += str;
      if(iterator.hasNext()){
        if(i % N == 0){
          result += NthConcator;
        }
        else {
          result += concator;
        }
      }
    }
    return result;
  }

  protected com.intellij.ide.util.ClassFilter createClassConditionFilter() {
    com.intellij.ide.util.ClassFilter classFilter;
    if(myBreakpointPsiClass != null) {
      classFilter = new com.intellij.ide.util.ClassFilter() {
        @Override
        public boolean isAccepted(PsiClass aClass) {
          return myBreakpointPsiClass == aClass || aClass.isInheritor(myBreakpointPsiClass, true);
        }
      };
    }
    else {
      classFilter = null;
    }
    return classFilter;
  }

  protected void updateCheckboxes() {
    boolean passCountApplicable = true;
    if (myInstanceFiltersCheckBox.isSelected() || myClassFiltersCheckBox.isSelected()) {
      passCountApplicable = false;
    }
    myPassCountCheckbox.setEnabled(passCountApplicable);

    final boolean passCountSelected = myPassCountCheckbox.isSelected();
    myInstanceFiltersCheckBox.setEnabled(!passCountSelected);
    myClassFiltersCheckBox.setEnabled(!passCountSelected);

    myPassCountField.setEditable(myPassCountCheckbox.isSelected());
    myPassCountField.setEnabled (myPassCountCheckbox.isSelected());

    myInstanceFiltersField.setEnabled(myInstanceFiltersCheckBox.isSelected());
    myInstanceFiltersField.getTextField().setEditable(myInstanceFiltersCheckBox.isSelected());

    myClassFiltersField.setEnabled(myClassFiltersCheckBox.isSelected());
    myClassFiltersField.getTextField().setEditable(myClassFiltersCheckBox.isSelected());
  }
}
