import {AsyncRoute, AuxRoute, Route, Redirect, RouteDefinition} from './route_config_decorator';
import {ComponentDefinition} from './route_definition';
import {isType, Type} from 'angular2/src/facade/lang';
import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
import {RouteRegistry} from './route_registry';


/**
 * Given a JS Object that represents a route config, returns a corresponding Route, AsyncRoute,
 * AuxRoute or Redirect object.
 *
 * Also wraps an AsyncRoute's loader function to add the loaded component's route config to the
 * `RouteRegistry`.
 */
export function normalizeRouteConfig(config: RouteDefinition,
                                     registry: RouteRegistry): RouteDefinition {
  if (config instanceof AsyncRoute) {
    var wrappedLoader = wrapLoaderToReconfigureRegistry(config.loader, registry);
    return new AsyncRoute({
      path: config.path,
      loader: wrappedLoader,
      name: config.name,
      data: config.data,
      useAsDefault: config.useAsDefault
    });
  }
  if (config instanceof Route || config instanceof Redirect || config instanceof AuxRoute) {
    return <RouteDefinition>config;
  }

  if ((+!!config.component) + (+!!config.redirectTo) + (+!!config.loader) != 1) {
    throw new BaseException(
        `Route config should contain exactly one "component", "loader", or "redirectTo" property.`);
  }
  if (config.as && config.name) {
    throw new BaseException(`Route config should contain exactly one "as" or "name" property.`);
  }
  if (config.as) {
    config.name = config.as;
  }
  if (config.loader) {
    var wrappedLoader = wrapLoaderToReconfigureRegistry(config.loader, registry);
    return new AsyncRoute({
      path: config.path,
      loader: wrappedLoader,
      name: config.name,
      useAsDefault: config.useAsDefault
    });
  }
  if (config.aux) {
    return new AuxRoute({path: config.aux, component:<Type>config.component, name: config.name});
  }
  if (config.component) {
    if (typeof config.component == 'object') {
      let componentDefinitionObject = <ComponentDefinition>config.component;
      if (componentDefinitionObject.type == 'constructor') {
        return new Route({
          path: config.path,
          component:<Type>componentDefinitionObject.constructor,
          name: config.name,
          data: config.data,
          useAsDefault: config.useAsDefault
        });
      } else if (componentDefinitionObject.type == 'loader') {
        return new AsyncRoute({
          path: config.path,
          loader: componentDefinitionObject.loader,
          name: config.name,
          useAsDefault: config.useAsDefault
        });
      } else {
        throw new BaseException(
            `Invalid component type "${componentDefinitionObject.type}". Valid types are "constructor" and "loader".`);
      }
    }
    return new Route(<{
      path: string;
      component: Type;
      name?: string;
      data?: {[key: string]: any};
      useAsDefault?: boolean;
    }>config);
  }

  if (config.redirectTo) {
    return new Redirect({path: config.path, redirectTo: config.redirectTo});
  }

  return config;
}


function wrapLoaderToReconfigureRegistry(loader: Function, registry: RouteRegistry): Function {
  return () => {
    return loader().then((componentType) => {
      registry.configFromComponent(componentType);
      return componentType;
    });
  };
}

export function assertComponentExists(component: Type, path: string): void {
  if (!isType(component)) {
    throw new BaseException(`Component for route "${path}" is not defined, or is not a class.`);
  }
}
