// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.debug.ui.propertypages;

import org.chromium.debug.ui.DialogUtils.Updater;
import org.chromium.debug.ui.DialogUtils.ValueConsumer;
import org.chromium.debug.ui.DialogUtils.ValueSource;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;

/**
 * UI control element that shows several last segments of the file path and allow to show more
 * or less. The segments is used in the system to match file in remote VM, thus showing more
 * segments makes matching more accurate. Accurateness value is the number of visible segments.
 */
class AccuratenessControl {
  private final Logic logic;

  AccuratenessControl(Composite composite, String[] pathSegments, int initialAccuratenessValue) {
    Composite inner;
    {
      inner = new Composite(composite, SWT.NONE);
      GridLayout layout = new GridLayout(1, false);
      layout.marginHeight = 0;
      inner.setLayout(layout);
      inner.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
    }

    Composite topLineBlock;
    {
      topLineBlock = new Composite(inner, SWT.NONE);
      GridLayout layout = new GridLayout(2, false);
      layout.marginHeight = 0;
      layout.marginWidth = 0;
      topLineBlock.setLayout(layout);
      topLineBlock.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
    }

    Label label = new Label(topLineBlock, SWT.NONE);
    label.setText(Messages.AccuratenessControl_LABEL_USE);

    final Button less;
    final Button more;
    {
      Composite buttonsGroup = new Composite(topLineBlock, SWT.NONE);
      GridLayout layout = new GridLayout(2, false);
      layout.marginHeight = 0;
      buttonsGroup.setLayout(layout);

      less = new Button(buttonsGroup, SWT.NONE);
      less.setText(Messages.AccuratenessControl_LESS_BUTTON);
      more = new Button(buttonsGroup, SWT.NONE);
      more.setText(Messages.AccuratenessControl_MORE_BUTTON);
    }


    final PathLabel pathLabel = new PathLabel(inner, pathSegments);
    pathLabel.getControl().setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

    pathLabel.setVisiblePart(initialAccuratenessValue);

    // Padding
    new Label(inner, SWT.NONE);

    Elements elements = new Elements() {
      @Override public AccuratenessControl.PathLabel getPathLabel() {
        return pathLabel;
      }
      @Override public Button getMoreButton() {
        return more;
      }
      @Override public Button getLessButton() {
        return less;
      }
    };

    logic = createLogic(elements);

    logic.updateAll();
  }

  int getAccuratenessValue() {
    return logic.getAccuratenessValue();
  }

  private static Logic createLogic(final Elements elements) {
    // The logic is Updater-based.
    // Presented path of several visible segments influences whether 'more' or 'less'
    // buttons enabled.
    final Updater updater = new Updater();

    final AccuratenessControl.PathLabel pathElement = elements.getPathLabel();
    final ValueSource<Integer> visiblePartSource = new ValueSource<Integer>() {
      @Override
      public Integer getValue() {
        return pathElement.getVisiblePartSize();
      }
    };

    updater.addSource(updater.rootScope(), visiblePartSource);

    // Wraps button as ValueConsumer.
    abstract class ButtonController implements ValueConsumer {
      private final Button button;

      ButtonController(Button button) {
        this.button = button;

        button.addSelectionListener(new SelectionListener() {
          @Override
          public void widgetSelected(SelectionEvent e) {
            int size = pathElement.getVisiblePartSize();
            if (!isEnabled(size)) {
              return;
            }
            int newSize = getNewValue(size);
            pathElement.setVisiblePart(newSize);
            updater.reportChanged(visiblePartSource);
            updater.update();
          }

          @Override
          public void widgetDefaultSelected(SelectionEvent e) {
          }
        });
      }

      @Override
      public void update(Updater updater) {
        int size = pathElement.getVisiblePartSize();
        boolean enabled = isEnabled(size);
        button.setEnabled(enabled);
      }

      // Defines button logic.
      protected abstract boolean isEnabled(int visiblePartSize);

      // Defines button logic.
      protected abstract int getNewValue(int currentValue);
    }

    ValueConsumer lessController = new ButtonController(elements.getLessButton()) {
      @Override
      protected boolean isEnabled(int visiblePartSize) {
        return visiblePartSize > 1;
      }

      @Override
      protected int getNewValue(int currentValue) {
        return currentValue - 1;
      }
    };

    updater.addConsumer(updater.rootScope(), lessController);
    updater.addDependency(lessController, visiblePartSource);

    final int size = elements.getPathLabel().size();
    ValueConsumer moreController = new ButtonController(elements.getMoreButton()) {
      @Override
      protected boolean isEnabled(int visiblePartSize) {
        return visiblePartSize < size;
      }

      @Override
      protected int getNewValue(int currentValue) {
        return currentValue + 1;
      }
    };

    updater.addConsumer(updater.rootScope(), moreController);
    updater.addDependency(moreController, visiblePartSource);

    return new Logic() {
      @Override
      public int getAccuratenessValue() {
        return elements.getPathLabel().getVisiblePartSize();
      }
      @Override
      public void updateAll() {
        updater.updateAll();
      }
    };
  }

  private interface Elements {
    AccuratenessControl.PathLabel getPathLabel();
    Button getLessButton();
    Button getMoreButton();
  }

  private interface Logic {
    int getAccuratenessValue();
    void updateAll();
  }

  /**
   * UI elements that shows several last segments of a particular file path
   * (called visible part size).
   * Last segment is always visible and is presented in bold font.
   */
  private static class PathLabel {
    private final String[] components;
    private final StyledText styledText;
    private int visiblePartSize = 1;

    /**
     * @param components input file path components
     */
    PathLabel(Composite composite, String[] components) {
      this.components = components;
      styledText = new StyledText(composite, SWT.NONE);
      styledText.setEditable(false);
      styledText.setCaret(null);
      styledText.setBackground(composite.getBackground());
    }

    public Control getControl() {
      return styledText;
    }

    int size() {
      return components.length;
    }

    int getVisiblePartSize() {
      return visiblePartSize;
    }

    void setVisiblePart(int visiblePartSize) {
      this.visiblePartSize = visiblePartSize;
      int size = components.length;
      StringBuilder builder = new StringBuilder();
      for (int i = size - visiblePartSize; i < size - 1; i++) {
        builder.append(components[i]);
        builder.append("/"); //$NON-NLS-1$
      }
      int lastComponentStart = builder.length();
      builder.append(components[size - 1]);
      int lastComponentEnd = builder.length();
      styledText.setText(builder.toString());
      StyleRange boldRange = new StyleRange(lastComponentStart,
          lastComponentEnd - lastComponentStart, null, null, SWT.BOLD);
      StyleRange[] ranges = { boldRange };
      styledText.setStyleRanges(ranges);
      styledText.setCaretOffset(lastComponentStart);
    }
  }
}