import {Injectable} from 'angular2/src/core/di';
import {
  PlatformLocation,
  UrlChangeEvent,
  UrlChangeListener
} from 'angular2/src/router/platform_location';
import {
  FnArg,
  UiArguments,
  ClientMessageBroker,
  ClientMessageBrokerFactory
} from 'angular2/src/web_workers/shared/client_message_broker';
import {ROUTER_CHANNEL} from 'angular2/src/web_workers/shared/messaging_api';
import {LocationType} from 'angular2/src/web_workers/shared/serialized_types';
import {Promise, PromiseWrapper, EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
import {BaseException} from 'angular2/src/facade/exceptions';
import {PRIMITIVE, Serializer} from 'angular2/src/web_workers/shared/serializer';
import {MessageBus} from 'angular2/src/web_workers/shared/message_bus';
import {StringMapWrapper} from 'angular2/src/facade/collection';
import {StringWrapper} from 'angular2/src/facade/lang';
import {deserializeGenericEvent} from './event_deserializer';

@Injectable()
export class WebWorkerPlatformLocation extends PlatformLocation {
  private _broker: ClientMessageBroker;
  private _popStateListeners: Array<Function> = [];
  private _hashChangeListeners: Array<Function> = [];
  private _location: LocationType = null;
  private _channelSource: EventEmitter<Object>;

  constructor(brokerFactory: ClientMessageBrokerFactory, bus: MessageBus,
              private _serializer: Serializer) {
    super();
    this._broker = brokerFactory.createMessageBroker(ROUTER_CHANNEL);

    this._channelSource = bus.from(ROUTER_CHANNEL);
    ObservableWrapper.subscribe(this._channelSource, (msg: {[key: string]: any}) => {
      var listeners: Array<Function> = null;
      if (StringMapWrapper.contains(msg, 'event')) {
        let type: string = msg['event']['type'];
        if (StringWrapper.equals(type, "popstate")) {
          listeners = this._popStateListeners;
        } else if (StringWrapper.equals(type, "hashchange")) {
          listeners = this._hashChangeListeners;
        }

        if (listeners !== null) {
          let e = deserializeGenericEvent(msg['event']);
          // There was a popState or hashChange event, so the location object thas been updated
          this._location = this._serializer.deserialize(msg['location'], LocationType);
          listeners.forEach((fn: Function) => fn(e));
        }
      }
    });
  }

  /** @internal **/
  init(): Promise<boolean> {
    var args: UiArguments = new UiArguments("getLocation");

    var locationPromise: Promise<LocationType> = this._broker.runOnService(args, LocationType);
    return PromiseWrapper.then(locationPromise, (val: LocationType): boolean => {
      this._location = val;
      return true;
    }, (err): boolean => { throw new BaseException(err); });
  }

  getBaseHrefFromDOM(): string {
    throw new BaseException(
        "Attempt to get base href from DOM from WebWorker. You must either provide a value for the APP_BASE_HREF token through DI or use the hash location strategy.");
  }

  onPopState(fn: UrlChangeListener): void { this._popStateListeners.push(fn); }

  onHashChange(fn: UrlChangeListener): void { this._hashChangeListeners.push(fn); }

  get pathname(): string {
    if (this._location === null) {
      return null;
    }

    return this._location.pathname;
  }

  get search(): string {
    if (this._location === null) {
      return null;
    }

    return this._location.search;
  }

  get hash(): string {
    if (this._location === null) {
      return null;
    }

    return this._location.hash;
  }

  set pathname(newPath: string) {
    if (this._location === null) {
      throw new BaseException("Attempt to set pathname before value is obtained from UI");
    }

    this._location.pathname = newPath;

    var fnArgs = [new FnArg(newPath, PRIMITIVE)];
    var args = new UiArguments("setPathname", fnArgs);
    this._broker.runOnService(args, null);
  }

  pushState(state: any, title: string, url: string): void {
    var fnArgs =
        [new FnArg(state, PRIMITIVE), new FnArg(title, PRIMITIVE), new FnArg(url, PRIMITIVE)];
    var args = new UiArguments("pushState", fnArgs);
    this._broker.runOnService(args, null);
  }

  replaceState(state: any, title: string, url: string): void {
    var fnArgs =
        [new FnArg(state, PRIMITIVE), new FnArg(title, PRIMITIVE), new FnArg(url, PRIMITIVE)];
    var args = new UiArguments("replaceState", fnArgs);
    this._broker.runOnService(args, null);
  }

  forward(): void {
    var args = new UiArguments("forward");
    this._broker.runOnService(args, null);
  }

  back(): void {
    var args = new UiArguments("back");
    this._broker.runOnService(args, null);
  }
}
