// 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.editor;

import collide.client.util.Elements;

import com.google.collide.shared.document.Line;
import com.google.collide.shared.document.LineInfo;
import com.google.collide.shared.document.anchor.Anchor;
import com.google.collide.shared.util.SortedList;

import elemental.css.CSSStyleDeclaration;
import elemental.dom.Element;
import elemental.events.Event;
import elemental.events.EventListener;

/*
 * TODO: Knowledge about what lines the spacer is logically linked
 * to which could make the system scroll intelligently, see comments in CL
 * 26294787
 */
/**
 * A spacer allows a client to insert UI inbetween lines in an editor without
 * affecting the backing document.
 *
 */
public class Spacer {

  /**
   * Comparator for sorting spacers.
   */
  public static class Comparator implements SortedList.Comparator<Spacer> {
    @Override
    public int compare(Spacer a, Spacer b) {
      return a.getLineNumber() - b.getLineNumber();
    }
  }

  /**
   * One way comparator for sorting spacers.
   */
  public static class OneWaySpacerComparator extends SortedList.OneWayIntComparator<Spacer> {
    @Override
    public int compareTo(Spacer s) {
      return value - s.getLineNumber();
    }
  }

  /** A spacer's anchor is always attached to the line that follows the spacer */
  private final Anchor anchor;
  private final Buffer buffer;
  private final CoordinateMap coordinateMap;
  /** Always use {@link #getElement()} */
  private Element element;
  private int height;
  private String cssClass;

  Spacer(Anchor anchor, int height, CoordinateMap coordinateMap, Buffer buffer, String cssClass) {
    this.anchor = anchor;
    this.height = height;
    this.coordinateMap = coordinateMap;
    this.buffer = buffer;
    this.cssClass = cssClass;
  }

  /**
   * Return the line number this spacer is attached above.
   */
  public int getLineNumber() {
    return anchor.getLineNumber();
  }

  public Line getLine() {
    return anchor.getLine();
  }

  public LineInfo getLineInfo() {
    return anchor.getLineInfo();
  }

  public Anchor getAnchor() {
    return anchor;
  }

  /**
   * Return the height of this spacer, not including the line it is attached
   * to below.
   */
  public int getHeight() {
    return height;
  }

  public void setHeight(int newHeight) {
    int oldHeight = height;
    height = newHeight;
    coordinateMap.handleSpacerHeightChanged(this, oldHeight);
    element.getStyle().setHeight(height, CSSStyleDeclaration.Unit.PX);
    element.getStyle().setMarginTop(-height, CSSStyleDeclaration.Unit.PX);
  }

  public void addElement(Element addElement) {
    getElement().appendChild(addElement);
  }

  public boolean isAttached() {
    return anchor.isAttached();
  }

  @Override
  public String toString() {
    return "Spacer, height: " + height + ", line number: " + anchor.getLineNumber();
  }

  private Element getElement() {
    if (element != null) {
      return element;
    }

    // Create div area for clients to draw inside
    element = Elements.createDivElement();
    element.addClassName(cssClass);
    element.getStyle().setLeft("0px");
    element.getStyle().setRight("0px");
    element.getStyle().setHeight(height, CSSStyleDeclaration.Unit.PX);
    element.getStyle().setPosition(CSSStyleDeclaration.Position.ABSOLUTE);
    element.getStyle().setMarginTop(-height, CSSStyleDeclaration.Unit.PX);

    EventListener bubblePreventionListener = new EventListener() {
      @Override
      public void handleEvent(Event e) {
        e.stopPropagation();
      }
    };

    element.setOnmousedown(bubblePreventionListener);
    element.setOnmousemove(bubblePreventionListener);
    element.setOnmouseup(bubblePreventionListener);

    buffer.addAnchoredElement(anchor, element);

    return element;
  }
}
