// Copyright 2012 Google Inc. All Rights Reserved.
//
// 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.google.collide.client.ui.tooltip;

import collide.client.util.CssUtils;
import collide.client.util.Elements;

import com.google.collide.client.ui.menu.PositionController;
import com.google.collide.client.ui.menu.PositionController.HorizontalAlign;
import com.google.collide.client.ui.menu.PositionController.VerticalAlign;
import com.google.collide.client.ui.tooltip.Coachmark.View;
import com.google.collide.mvp.CompositeView;
import com.google.collide.mvp.UiComponent;
import com.google.gwt.resources.client.CssResource;

import elemental.css.CSSStyleDeclaration;
import elemental.dom.Element;
import elemental.html.DivElement;

/**
 * An object which represents a coach mark (a tooltip like notification which is often dismissable).
 */
// TODO: Generalize the class, it currently isn't so general.
public class Coachmark extends UiComponent<View> {

  public static Coachmark create(
      Tooltip.Resources res, Renderer renderer, PositionController.Positioner positioner) {
    View view = new View(res.coachmarkCss());
    return new Coachmark(view, renderer, new PositionController(positioner, view.getElement()));
  }

  public interface Css extends CssResource {
    public String base();

    public String container();

    public String arrow();

    public String arrowInner();

    public String alert();

    public String disclaimer();

    public int arrowSize();
  }

  /**
   * A renderer which renders the contents of a {@link Coachmark}.
   */
  public interface Renderer {
    /**
     * @param container the container to which any child elements should be attached.
     */
    public void render(Element container, Coachmark coachmark);
  }

  public static class BasicRenderer implements Renderer {
    private final String text;

    public BasicRenderer(String text, Coachmark coachmark) {
      this.text = text;
    }

    @Override
    public void render(Element container, Coachmark coachmark) {
      container.setTextContent(text);
    }
  }

  public static class View extends CompositeView<Void> {

    private final DivElement container;
    private final DivElement arrow;
    private final DivElement arrowInner;
    private final Css css;

    public View(Css css) {
      super(Elements.createDivElement(css.base()));
      this.css = css;
      CssUtils.setDisplayVisibility2(getElement(), false);

      this.container = Elements.createDivElement(css.container());
      this.arrow = Elements.createDivElement(css.arrow());
      this.arrowInner = Elements.createDivElement(css.arrowInner());

      arrow.appendChild(arrowInner);
      getElement().appendChild(arrow);
      getElement().appendChild(container);
    }

    public Element getArrow() {
      return arrow;
    }

    public Element getContainer() {
      return container;
    }
  }

  private final Renderer renderer;
  private final PositionController positionController;
  private boolean hasRendered = false;

  private Coachmark(View view, Renderer renderer, PositionController positionController) {
    super(view);
    this.renderer = renderer;
    this.positionController = positionController;
  }

  public void show() {
    Element element = getView().getElement();
    if (!hasRendered) {
      renderer.render(getView().getContainer(), this);
      hasRendered = true;
    }

    updatePosition();
    CssUtils.setDisplayVisibility2(element, true);
  }

  public void hide() {
    Element element = getView().getElement();
    CssUtils.setDisplayVisibility2(element, false);
  }

  private void updatePosition() {
    PositionController.Positioner positioner = positionController.getPositioner();
    int x = 0;
    int y = getYOffset(positioner.getVerticalAlignment());

    // TODO: This is megahacked together, if you try to do a coach mark get here and find
    // you need better, we should refactor this so it is more general.
    updateArrow(getXOffset(positioner.getHorizontalAlignment()), y);
    positionController.updateElementPosition(x, y);
  }

  private void updateArrow(int x, int y) {
    Element arrow = getView().getArrow();
    CSSStyleDeclaration style = arrow.getStyle();

    HorizontalAlign alignment = positionController.getPositioner().getHorizontalAlignment();
    style.setLeft("auto");
    style.setRight("auto");
    if (alignment == HorizontalAlign.LEFT) {
      style.setLeft(-(x * 2), CSSStyleDeclaration.Unit.PX);
    } else {
      style.setRight((x * 2), CSSStyleDeclaration.Unit.PX);
    }
    style.setTop(-(y * 2), CSSStyleDeclaration.Unit.PX);
  }

  private int getYOffset(VerticalAlign alignment) {
    int arrowSize = getView().css.arrowSize();
    switch (alignment) {
      case TOP:
        return -arrowSize;
      case BOTTOM:
        return arrowSize;
      default:
        return 0;
    }
  }

  private int getXOffset(HorizontalAlign alignment) {
    int arrowSize = getView().css.arrowSize();
    switch (alignment) {
      case LEFT:
        return -arrowSize;
      case RIGHT:
        return arrowSize;
      default:
        return 0;
    }
  }
}
