package com.yoghurt.crypto.transactions.client.widget;

import java.util.ArrayList;

import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Style.Visibility;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseOutHandler;
import com.google.gwt.event.dom.client.MouseOverEvent;
import com.google.gwt.event.dom.client.MouseOverHandler;
import com.google.gwt.event.logical.shared.AttachEvent;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Widget;
import com.yoghurt.crypto.transactions.client.resources.R;
import com.yoghurt.crypto.transactions.client.util.misc.Color;

public class ContextFieldSet<T> extends FlowPanel {
  private static final int POPUP_SHOW_DELAY = 50;
  private static final int POPUP_HIDE_DELAY = 70;

  protected ArrayList<ContextField<T>> fields = new ArrayList<ContextField<T>>();

  public interface FieldContextFactory<T> {
    Widget getContextWidget(T part);
  }

  private final class TypedTimer extends Timer {
    private final ContextField<T> source;

    public TypedTimer(final ContextField<T> source) {
      this.source = source;
    }

    @Override
    public void run() {
      if (selectedField == null || selectedField == source) {
        displayContextPopup(source);
      }
    }
  }

  private final Timer popupHideTimer = new Timer() {
    @Override
    public void run() {
      clearActivity();
    }
  };

  private final MouseOverHandler mouseOverHandler = new MouseOverHandler() {
    @SuppressWarnings("unchecked")
    @Override
    public void onMouseOver(final MouseOverEvent event) {
      popupHideTimer.cancel();

      if (popupShowTimer != null) {
        popupShowTimer.cancel();
      }

      popupShowTimer = new TypedTimer((ContextField<T>) event.getSource());
      popupShowTimer.schedule(popupPanel.isShowing() ? 0 : POPUP_SHOW_DELAY);
    }
  };

  private final MouseOutHandler mouseOutHandler = new MouseOutHandler() {
    @Override
    public void onMouseOut(final MouseOutEvent event) {
      if (selectedField == null) {
        popupShowTimer.cancel();
        delayedHide();
      }
    }
  };

  private ClickHandler mouseClickHandler = new ClickHandler() {
    @SuppressWarnings("unchecked")
    @Override
    public void onClick(final ClickEvent event) {
      if (selectedField != null) {
        selectedField.setSelected(false);
      }

      if (selectedField == event.getSource()) {
        selectedField = null;
        return;
      }

      selectedField = (ContextField<T>) event.getSource();
      selectedField.setSelected(true);

      selectedField.fireEvent(new MouseOverEvent() {});
    }
  };

  private FieldContextFactory<T> contextFactory;

  private final AttachedContextPanel popupPanel = new AttachedContextPanel();
  private Timer popupShowTimer;

  private ContextField<T> selectedField;
  private final AttachEvent.Handler attachHandler = new AttachEvent.Handler() {

    @Override
    public void onAttachOrDetach(final AttachEvent event) {
      if (!event.isAttached()) {
        popupPanel.hide();
        if (attachRegistration != null) {
          attachRegistration.removeHandler();
        }
      }
    }

  };

  private HandlerRegistration attachRegistration;

  public ContextFieldSet() {}

  public ContextFieldSet(final FieldContextFactory<T> contextFactory) {
    this.contextFactory = contextFactory;
    
    setStyleName(R.css().flex());
  }

  public ContextFieldSet(final String text) {
    this(new TextContextFactory<T>(text));
  }

  @Override
  public void clear() {
    super.clear();
    fields.clear();
    clearActivity();
  }

  protected void clearActivity() {
    selectedField = null;
    popupPanel.hide();
  }

  public void addField(final T value) {
    addField(value, getFieldColor(value));
  }

  public void addField(final T value, final Color color) {
    addField(value, color, getFieldText(value));
  }

  public ContextField<T> addField(final T value, final Color color, final String text) {
    final ContextField<T> field = createContextField(value, color, text);

    field.addMouseOverHandler(mouseOverHandler);
    field.addMouseOutHandler(mouseOutHandler);
    field.addClickHandler(mouseClickHandler);

    fields.add(field);
    return addField(field);
  }

  protected ContextField<T> createContextField(final T value, final Color color, final String text) {
    return new ContextField<T>(value, color, text);
  }

  protected Color getFieldColor(final T value) {
    // No-op by default
    return null;
  }

  protected String getFieldText(final T value) {
    // No-op by default
    return null;
  }

  protected ContextField<T> addField(final ContextField<T> field) {
    add(field);

    return field;
  }

  protected void removeField(final int counter) {
    fields.remove(counter);
    remove(counter);
  }

  private void delayedHide() {
    popupHideTimer.schedule(POPUP_HIDE_DELAY);
  }

  // TODO delegate context styling out of here
  private void displayContextPopup(final ContextField<T> field) {
    if (contextFactory == null) {
      return;
    }

    // Create a new content widget
    final Widget popupContent = contextFactory.getContextWidget(field.getValue());

    final ContextPanel panel = new ContextPanel();
    panel.setWidget(popupContent);

    final Color borderColor = getFieldColor(field.getValue()).copy();
    final Color backgroundColor = borderColor.copy();
    borderColor.setA(0.8);
    backgroundColor.setA(0.01);

    panel.getElement().getStyle().setBorderColor(borderColor.getValue());
    popupContent.getElement().getStyle().setBackgroundColor(backgroundColor.getValue());

    // Notify display
    displayContextForValue(field.getValue());

    // Display the popup
    displayContextPopup(field, panel);
  }

  protected void displayContextForValue(final T value) {
    // No-op by default
  }

  private void displayContextPopup(final Widget target, final Widget popupContent) {
    if (!popupPanel.isShowing()) {
      popupContent.getElement().getStyle().setVisibility(Visibility.HIDDEN);
    }

    popupPanel.setWidget(popupContent);
    popupPanel.show();
    attachRegistration = target.addAttachHandler(attachHandler);

    // Defer the attach event because we don't have the element's width/height at this point
    Scheduler.get().scheduleDeferred(new ScheduledCommand() {
      @Override
      public void execute() {
        popupPanel.attachToWidget(target);
        popupContent.getElement().getStyle().clearVisibility();
      }
    });
  }

  public void setMouseClickHandler(final ClickHandler mouseClickHandler) {
    this.mouseClickHandler = mouseClickHandler;
  }

  public void setContextFactory(FieldContextFactory<T> contextFactory) {
    this.contextFactory = contextFactory;
  }
}