import {unimplemented} from 'angular2/src/facade/exceptions';
import {Map} from 'angular2/src/facade/collection';
import {ViewEncapsulation} from 'angular2/src/core/metadata';

/**
 * Represents an Angular ProtoView in the Rendering Context.
 *
 * When you implement a custom {@link Renderer}, `RenderProtoViewRef` specifies what Render View
 * your renderer should create.
 *
 * `RenderProtoViewRef` is a counterpart to {@link ProtoViewRef} available in the Application
 * Context. But unlike `ProtoViewRef`, `RenderProtoViewRef` contains all static nested Proto Views
 * that are recursively merged into a single Render Proto View.

 *
 * <!-- TODO: this is created by Renderer#createProtoView in the new compiler -->
 */
export class RenderProtoViewRef {}

/**
 * Represents a list of sibling Nodes that can be moved by the {@link Renderer} independently of
 * other Render Fragments.
 *
 * Any {@link RenderView} has one Render Fragment.
 *
 * Additionally any View with an Embedded View that contains a {@link NgContent View Projection}
 * results in additional Render Fragment.
 */
/*
  <div>foo</div>
  {{bar}}


  <div>foo</div> -> view 1 / fragment 1
  <ul>
    <template ng-for>
      <li>{{fg}}</li> -> view 2 / fragment 1
    </template>
  </ul>
  {{bar}}


  <div>foo</div> -> view 1 / fragment 1
  <ul>
    <template ng-if>
      <li><ng-content></></li> -> view 1 / fragment 2
    </template>
    <template ng-for>
      <li><ng-content></></li> ->
      <li></li>                -> view 1 / fragment 2 + view 2 / fragment 1..n-1
    </template>
  </ul>
  {{bar}}
 */
// TODO(i): refactor into an interface
export class RenderFragmentRef {}


/**
 * Represents an Angular View in the Rendering Context.
 *
 * `RenderViewRef` specifies to the {@link Renderer} what View to update or destroy.
 *
 * Unlike a {@link ViewRef} available in the Application Context, Render View contains all the
 * static Component Views that have been recursively merged into a single Render View.
 *
 * Each `RenderViewRef` contains one or more {@link RenderFragmentRef Render Fragments}, these
 * Fragments are created, hydrated, dehydrated and destroyed as a single unit together with the
 * View.
 */
// TODO(i): refactor into an interface
export class RenderViewRef {}

export abstract class RenderTemplateCmd {
  abstract visit(visitor: RenderCommandVisitor, context: any): any;
}

export abstract class RenderBeginCmd extends RenderTemplateCmd {
  get ngContentIndex(): number { return unimplemented(); };
  get isBound(): boolean { return unimplemented(); };
}

export abstract class RenderTextCmd extends RenderBeginCmd {
  get value(): string { return unimplemented(); };
}

export abstract class RenderNgContentCmd extends RenderTemplateCmd {
  // The index of this NgContent element
  get index(): number { return unimplemented(); };
  // The index of the NgContent element into which this
  // NgContent element should be projected (if any)
  get ngContentIndex(): number { return unimplemented(); };
}

export abstract class RenderBeginElementCmd extends RenderBeginCmd {
  get name(): string { return unimplemented(); };
  get attrNameAndValues(): string[] { return unimplemented(); };
  get eventTargetAndNames(): string[] { return unimplemented(); };
}

export abstract class RenderBeginComponentCmd extends RenderBeginElementCmd {
  get templateId(): string { return unimplemented(); };
}

export abstract class RenderEmbeddedTemplateCmd extends RenderBeginElementCmd {
  get isMerged(): boolean { return unimplemented(); };
  get children(): RenderTemplateCmd[] { return unimplemented(); };
}

export interface RenderCommandVisitor {
  visitText(cmd: RenderTextCmd, context: any): any;
  visitNgContent(cmd: RenderNgContentCmd, context: any): any;
  visitBeginElement(cmd: RenderBeginElementCmd, context: any): any;
  visitEndElement(context: any): any;
  visitBeginComponent(cmd: RenderBeginComponentCmd, context: any): any;
  visitEndComponent(context: any): any;
  visitEmbeddedTemplate(cmd: RenderEmbeddedTemplateCmd, context: any): any;
}


/**
 * Container class produced by a {@link Renderer} when creating a Render View.
 *
 * An instance of `RenderViewWithFragments` contains a {@link RenderViewRef} and an array of
 * {@link RenderFragmentRef}s belonging to this Render View.
 */
// TODO(i): refactor this by RenderViewWithFragments and adding fragments directly to RenderViewRef
export class RenderViewWithFragments {
  constructor(
      /**
       * Reference to the {@link RenderViewRef}.
       */
      public viewRef: RenderViewRef,
      /**
       * Array of {@link RenderFragmentRef}s ordered in the depth-first order.
       */
      public fragmentRefs: RenderFragmentRef[]) {}
}

/**
 * Represents an Element that is part of a {@link RenderViewRef Render View}.
 *
 * `RenderElementRef` is a counterpart to {@link ElementRef} available in the Application Context.
 *
 * When using `Renderer` from the Application Context, `ElementRef` can be used instead of
 * `RenderElementRef`.
 */
export interface RenderElementRef {
  /**
   * Reference to the Render View that contains this Element.
   */
  renderView: RenderViewRef;

  /**
   * @internal
   *
   * Index of the Element (in the depth-first order) inside the Render View.
   *
   * This index is used internally by Angular to locate elements.
   */
  boundElementIndex: number;
}

export class RenderComponentTemplate {
  constructor(public id: string, public shortId: string, public encapsulation: ViewEncapsulation,
              public commands: RenderTemplateCmd[], public styles: string[]) {}
}

/**
 * Injectable service that provides a low-level interface for modifying the UI.
 *
 * Use this service to bypass Angular's templating and make custom UI changes that can't be
 * expressed declaratively. For example if you need to set a property or an attribute whose name is
 * not statically known, use {@link #setElementProperty} or {@link #setElementAttribute}
 * respectively.
 *
 * If you are implementing a custom renderer, you must implement this interface.
 *
 * The default Renderer implementation is {@link DomRenderer}. Also see {@link WebWorkerRenderer}.
 */
export abstract class Renderer {
  /**
   * Registers a component template represented as arrays of {@link RenderTemplateCmd}s and styles
   * with the Renderer.
   *
   * Once a template is registered it can be referenced via {@link RenderBeginComponentCmd} when
   * {@link #createProtoView creating Render ProtoView}.
   */
  abstract registerComponentTemplate(template: RenderComponentTemplate);

  /**
   * Creates a {@link RenderProtoViewRef} from an array of {@link RenderTemplateCmd}`s.
   */
  abstract createProtoView(componentTemplateId: string,
                           cmds: RenderTemplateCmd[]): RenderProtoViewRef;

  /**
   * Creates a Root Host View based on the provided `hostProtoViewRef`.
   *
   * `fragmentCount` is the number of nested {@link RenderFragmentRef}s in this View. This parameter
   * is non-optional so that the renderer can create a result synchronously even when application
   * runs in a different context (e.g. in a Web Worker).
   *
   * `hostElementSelector` is a (CSS) selector for querying the main document to find the Host
   * Element. The newly created Root Host View should be attached to this element.
   *
   * Returns an instance of {@link RenderViewWithFragments}, representing the Render View.
   */
  abstract createRootHostView(hostProtoViewRef: RenderProtoViewRef, fragmentCount: number,
                              hostElementSelector: string): RenderViewWithFragments;

  /**
   * Creates a Render View based on the provided `protoViewRef`.
   *
   * `fragmentCount` is the number of nested {@link RenderFragmentRef}s in this View. This parameter
   * is non-optional so that the renderer can create a result synchronously even when application
   * runs in a different context (e.g. in a Web Worker).
   *
   * Returns an instance of {@link RenderViewWithFragments}, representing the Render View.
   */
  abstract createView(protoViewRef: RenderProtoViewRef,
                      fragmentCount: number): RenderViewWithFragments;

  /**
   * Destroys a Render View specified via `viewRef`.
   *
   * This operation should be performed only on a View that has already been dehydrated and
   * all of its Render Fragments have been detached.
   *
   * Destroying a View indicates to the Renderer that this View is not going to be referenced in any
   * future operations. If the Renderer created any renderer-specific objects for this View, these
   * objects should now be destroyed to prevent memory leaks.
   */
  abstract destroyView(viewRef: RenderViewRef);

  /**
   * Attaches the Nodes of a Render Fragment after the last Node of `previousFragmentRef`.
   */
  abstract attachFragmentAfterFragment(previousFragmentRef: RenderFragmentRef,
                                       fragmentRef: RenderFragmentRef);

  /**
   * Attaches the Nodes of the Render Fragment after an Element.
   */
  abstract attachFragmentAfterElement(elementRef: RenderElementRef, fragmentRef: RenderFragmentRef);

  /**
   * Detaches the Nodes of a Render Fragment from their parent.
   *
   * This operations should be called only on a View that has been already
   * {@link #dehydrateView dehydrated}.
   */
  abstract detachFragment(fragmentRef: RenderFragmentRef);

  /**
   * Notifies a custom Renderer to initialize a Render View.
   *
   * This method is called by Angular after a Render View has been created, or when a previously
   * dehydrated Render View is about to be reused.
   */
  abstract hydrateView(viewRef: RenderViewRef);

  /**
   * Notifies a custom Renderer that a Render View is no longer active.
   *
   * This method is called by Angular before a Render View will be destroyed, or when a hydrated
   * Render View is about to be put into a pool for future reuse.
   */
  abstract dehydrateView(viewRef: RenderViewRef);

  /**
   * Returns the underlying native element at the specified `location`, or `null` if direct access
   * to native elements is not supported (e.g. when the application runs in a web worker).
   *
   * <div class="callout is-critical">
   *   <header>Use with caution</header>
   *   <p>
   *    Use this api as the last resort when direct access to DOM is needed. Use templating and
   *    data-binding, or other {@link Renderer} methods instead.
   *   </p>
   *   <p>
   *    Relying on direct DOM access creates tight coupling between your application and rendering
   *    layers which will make it impossible to separate the two and deploy your application into a
   *    web worker.
   *   </p>
   * </div>
   */
  abstract getNativeElementSync(location: RenderElementRef): any;

  /**
   * Sets a property on the Element specified via `location`.
   */
  abstract setElementProperty(location: RenderElementRef, propertyName: string, propertyValue: any);

  /**
   * Sets an attribute on the Element specified via `location`.
   *
   * If `attributeValue` is `null`, the attribute is removed.
   */
  abstract setElementAttribute(location: RenderElementRef, attributeName: string,
                               attributeValue: string);

  /**
   * Sets a (CSS) class on the Element specified via `location`.
   *
   * `isAdd` specifies if the class should be added or removed.
   */
  abstract setElementClass(location: RenderElementRef, className: string, isAdd: boolean);

  /**
   * Sets a (CSS) inline style on the Element specified via `location`.
   *
   * If `styleValue` is `null`, the style is removed.
   */
  abstract setElementStyle(location: RenderElementRef, styleName: string, styleValue: string);

  /**
   * Calls a method on the Element specified via `location`.
   */
  abstract invokeElementMethod(location: RenderElementRef, methodName: string, args: any[]);

  /**
   * Sets the value of an interpolated TextNode at the specified index to the `text` value.
   *
   * `textNodeIndex` is the depth-first index of the Node among interpolated Nodes in the Render
   * View.
   */
  abstract setText(viewRef: RenderViewRef, textNodeIndex: number, text: string);

  /**
   * Sets a dispatcher to relay all events triggered in the given Render View.
   *
   * Each Render View can have only one Event Dispatcher, if this method is called multiple times,
   * the last provided dispatcher will be used.
   */
  abstract setEventDispatcher(viewRef: RenderViewRef, dispatcher: RenderEventDispatcher);
}

/**
 * A dispatcher that relays all events that occur in a Render View.
 *
 * Use {@link Renderer#setEventDispatcher} to register a dispatcher for a particular Render View.
 */
export interface RenderEventDispatcher {
  /**
   * Called when Event called `eventName` was triggered on an Element with an Event Binding for this
   * Event.
   *
   * `elementIndex` specifies the depth-first index of the Element in the Render View.
   *
   * `locals` is a map for local variable to value mapping that should be used when evaluating the
   * Event Binding expression.
   *
   * Returns `false` if `preventDefault` should be called to stop the default behavior of the Event
   * in the Rendering Context.
   */
  dispatchRenderEvent(elementIndex: number, eventName: string, locals: Map<string, any>): boolean;
}
