/*
 * Copyright 2000-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 com.intellij.util.ui;

import com.intellij.openapi.util.SystemInfo;
import com.intellij.util.ui.accessibility.AccessibleContextUtil;

import javax.accessibility.AccessibleContext;
import javax.accessibility.AccessibleRole;
import javax.accessibility.AccessibleState;
import javax.accessibility.AccessibleStateSet;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ItemEvent;

/**
 * @author spleaner
 */
public class ThreeStateCheckBox extends JCheckBox {
  private State myState;
  private boolean myThirdStateEnabled = true;

  public enum State {
    SELECTED, NOT_SELECTED, DONT_CARE
  }

  public ThreeStateCheckBox() {
    this(null, null, State.DONT_CARE);
  }

  public ThreeStateCheckBox(final State initial) {
    this(null, null, initial);
  }

  public ThreeStateCheckBox(final String text) {
    this(text, null, State.DONT_CARE);
  }

  public ThreeStateCheckBox(final String text, final State initial) {
    this(text, null, initial);
  }

  public ThreeStateCheckBox(final String text, final Icon icon) {
    this(text, icon, State.DONT_CARE);
  }

  public ThreeStateCheckBox(final String text, final Icon icon, final State initial) {
    super(text, icon);

    setModel(new ToggleButtonModel() {
      @Override
      public void setSelected(boolean selected) {
        setState(nextState());
        fireStateChanged();
        fireItemStateChanged(new ItemEvent(this, ItemEvent.ITEM_STATE_CHANGED, this, ItemEvent.SELECTED));
      }

      @Override
      public boolean isSelected() {
        return myState == State.SELECTED || (UIUtil.isUnderAquaLookAndFeel() && myState == State.DONT_CARE);
      }
    });

    setState(initial);
  }

  private State nextState() {
    switch (myState) {
      case SELECTED:
        return State.NOT_SELECTED;
      case NOT_SELECTED:
        if (myThirdStateEnabled) {
          return State.DONT_CARE;
        }
        else {
          return State.SELECTED;
        }
      default:
        return State.SELECTED;
    }
  }

  public boolean isThirdStateEnabled() {
    return myThirdStateEnabled;
  }

  public void setThirdStateEnabled(final boolean thirdStateEnabled) {
    myThirdStateEnabled = thirdStateEnabled;
  }

  @Override
  public void setSelected(final boolean b) {
    setState(b ? State.SELECTED : State.NOT_SELECTED);
  }

  public void setState(State state) {
    myState = state;

    String value = state == State.DONT_CARE ? "indeterminate" : null;
    putClientProperty("JButton.selectedState", value);

    repaint();
  }

  public State getState() {
    return myState;
  }


  @Override
  protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    if (UIUtil.isUnderAquaLookAndFeel() || (SystemInfo.isMac && UIUtil.isUnderIntelliJLaF())) {
      return;
    }

    switch (getState()) {
      case DONT_CARE:
        Icon icon = getIcon();
        if (icon == null) {
          icon = UIManager.getIcon("CheckBox.icon");
        }
        if (UIUtil.isUnderDarcula() || UIUtil.isUnderIntelliJLaF()) {
          icon = JBUI.scale(EmptyIcon.create(20, 18));
        }
        if (icon != null) {
          final Insets i = getInsets();
          final Rectangle r = getBounds();
          final Rectangle r1 = new Rectangle();
          r1.x = i.left;
          r1.y = i.top;
          r1.width = r.width - (i.right + r1.x);
          r1.height = r.height - (i.bottom + r1.y);

          final Rectangle r2 = new Rectangle();
          final Rectangle r3 = new Rectangle();
          SwingUtilities.layoutCompoundLabel(
            this, getFontMetrics(getFont()), getText(), icon,
            getVerticalAlignment(), getHorizontalAlignment(),
            getVerticalTextPosition(), getHorizontalTextPosition(),
            r1, r2, r3,
            getText() == null ? 0 : getIconTextGap());

          // selected table cell: do not paint white on white
          g.setColor(UIUtil.getTreeForeground());
          int height = r2.height / 10;
          int width = r2.width / 3;
          g.fillRect(r2.x + r2.width / 2 - width / 2, r2.y + r2.height / 2 - height / 2, width, height);
        }
        break;
      default:
        break;
    }
  }

  @Override
  public AccessibleContext getAccessibleContext() {
    if (accessibleContext == null) {
      accessibleContext = new AccessibleThreeStateCheckBox();
    }
    return accessibleContext;
  }

  /**
   * Emulate accessibility behavior of tri-state checkboxes, as tri-state checkboxes
   * are not part of the JAB specification.
   */
  protected class AccessibleThreeStateCheckBox extends AccessibleJCheckBox {
    @Override
    public AccessibleRole getAccessibleRole() {
      if (myThirdStateEnabled) {
        // Return TOGGLE_BUTTON so that screen readers don't announce the "not checked" state
        return AccessibleRole.TOGGLE_BUTTON;
      }

      return super.getAccessibleRole();
    }

    @Override
    public AccessibleStateSet getAccessibleStateSet() {
      if (myThirdStateEnabled) {
        // Remove CHECKED so that screen readers don't announce the "checked" state
        AccessibleStateSet set = super.getAccessibleStateSet();
        set.remove(AccessibleState.CHECKED);
        return set;
      }

      return super.getAccessibleStateSet();
    }

    @Override
    public String getAccessibleName() {
      if (myThirdStateEnabled) {
        // Add a state description suffix to the accessible name, so that screen readers
        // announce the state as part of the accessible name.
        return addStateDescription(super.getAccessibleName());
      }
      return super.getAccessibleName();
    }

    private String addStateDescription(String name) {
      switch(getState()) {
        case SELECTED:
          return AccessibleContextUtil.combineAccessibleStrings(name, " ", "checked");
        case NOT_SELECTED:
          return AccessibleContextUtil.combineAccessibleStrings(name, " ", "not checked");
        case DONT_CARE:
          return AccessibleContextUtil.combineAccessibleStrings(name, " ", "partially checked");
        default:
          return name;
      }
    }
  }
}
