import {ChangeDetectionStrategy, ViewEncapsulation} from '@angular/core';
import {
  CHANGE_DETECTION_STRATEGY_VALUES,
  VIEW_ENCAPSULATION_VALUES,
  LifecycleHooks,
  LIFECYCLE_HOOKS_VALUES
} from '../core_private';

import {
  isPresent,
  isBlank,
  isNumber,
  isBoolean,
  normalizeBool,
  normalizeBlank,
  serializeEnum,
  Type,
  isString,
  RegExpWrapper,
  isArray
} from '../src/facade/lang';
import {unimplemented, BaseException} from '../src/facade/exceptions';
import {
  StringMapWrapper,
} from '../src/facade/collection';
import {CssSelector} from './selector';
import {splitAtColon, sanitizeIdentifier} from './util';
import {getUrlScheme} from './url_resolver';

// group 1: "property" from "[property]"
// group 2: "event" from "(event)"
var HOST_REG_EXP = /^(?:(?:\[([^\]]+)\])|(?:\(([^\)]+)\)))$/g;

export abstract class CompileMetadataWithIdentifier {
  abstract toJson(): {[key: string]: any};

  get identifier(): CompileIdentifierMetadata { return <CompileIdentifierMetadata>unimplemented(); }
}

export abstract class CompileMetadataWithType extends CompileMetadataWithIdentifier {
  abstract toJson(): {[key: string]: any};

  get type(): CompileTypeMetadata { return <CompileTypeMetadata>unimplemented(); }

  get identifier(): CompileIdentifierMetadata { return <CompileIdentifierMetadata>unimplemented(); }
}

export function metadataFromJson(data: {[key: string]: any}): any {
  return _COMPILE_METADATA_FROM_JSON[data['class']](data);
}

export class CompileIdentifierMetadata implements CompileMetadataWithIdentifier {
  runtime: any;
  name: string;
  prefix: string;
  moduleUrl: string;
  value: any;

  constructor(
      {runtime, name, moduleUrl, prefix, value}:
          {runtime?: any, name?: string, moduleUrl?: string, prefix?: string, value?: any} = {}) {
    this.runtime = runtime;
    this.name = name;
    this.prefix = prefix;
    this.moduleUrl = moduleUrl;
    this.value = value;
  }

  static fromJson(data: {[key: string]: any}): CompileIdentifierMetadata {
    let value = isArray(data['value']) ? _arrayFromJson(data['value'], metadataFromJson) :
                                         _objFromJson(data['value'], metadataFromJson);
    return new CompileIdentifierMetadata(
        {name: data['name'], prefix: data['prefix'], moduleUrl: data['moduleUrl'], value: value});
  }

  toJson(): {[key: string]: any} {
    let value = isArray(this.value) ? _arrayToJson(this.value) : _objToJson(this.value);
    return {
      // Note: Runtime type can't be serialized...
      'class': 'Identifier',
      'name': this.name,
      'moduleUrl': this.moduleUrl,
      'prefix': this.prefix,
      'value': value
    };
  }

  get identifier(): CompileIdentifierMetadata { return this; }
}

export class CompileDiDependencyMetadata {
  isAttribute: boolean;
  isSelf: boolean;
  isHost: boolean;
  isSkipSelf: boolean;
  isOptional: boolean;
  isValue: boolean;
  query: CompileQueryMetadata;
  viewQuery: CompileQueryMetadata;
  token: CompileTokenMetadata;
  value: any;

  constructor({isAttribute, isSelf, isHost, isSkipSelf, isOptional, isValue, query, viewQuery,
               token, value}: {
    isAttribute?: boolean,
    isSelf?: boolean,
    isHost?: boolean,
    isSkipSelf?: boolean,
    isOptional?: boolean,
    isValue?: boolean,
    query?: CompileQueryMetadata,
    viewQuery?: CompileQueryMetadata,
    token?: CompileTokenMetadata,
    value?: any
  } = {}) {
    this.isAttribute = normalizeBool(isAttribute);
    this.isSelf = normalizeBool(isSelf);
    this.isHost = normalizeBool(isHost);
    this.isSkipSelf = normalizeBool(isSkipSelf);
    this.isOptional = normalizeBool(isOptional);
    this.isValue = normalizeBool(isValue);
    this.query = query;
    this.viewQuery = viewQuery;
    this.token = token;
    this.value = value;
  }

  static fromJson(data: {[key: string]: any}): CompileDiDependencyMetadata {
    return new CompileDiDependencyMetadata({
      token: _objFromJson(data['token'], CompileTokenMetadata.fromJson),
      query: _objFromJson(data['query'], CompileQueryMetadata.fromJson),
      viewQuery: _objFromJson(data['viewQuery'], CompileQueryMetadata.fromJson),
      value: data['value'],
      isAttribute: data['isAttribute'],
      isSelf: data['isSelf'],
      isHost: data['isHost'],
      isSkipSelf: data['isSkipSelf'],
      isOptional: data['isOptional'],
      isValue: data['isValue']
    });
  }

  toJson(): {[key: string]: any} {
    return {
      'token': _objToJson(this.token),
      'query': _objToJson(this.query),
      'viewQuery': _objToJson(this.viewQuery),
      'value': this.value,
      'isAttribute': this.isAttribute,
      'isSelf': this.isSelf,
      'isHost': this.isHost,
      'isSkipSelf': this.isSkipSelf,
      'isOptional': this.isOptional,
      'isValue': this.isValue
    };
  }
}

export class CompileProviderMetadata {
  token: CompileTokenMetadata;
  useClass: CompileTypeMetadata;
  useValue: any;
  useExisting: CompileTokenMetadata;
  useFactory: CompileFactoryMetadata;
  deps: CompileDiDependencyMetadata[];
  multi: boolean;

  constructor({token, useClass, useValue, useExisting, useFactory, deps, multi}: {
    token?: CompileTokenMetadata,
    useClass?: CompileTypeMetadata,
    useValue?: any,
    useExisting?: CompileTokenMetadata,
    useFactory?: CompileFactoryMetadata,
    deps?: CompileDiDependencyMetadata[],
    multi?: boolean
  }) {
    this.token = token;
    this.useClass = useClass;
    this.useValue = useValue;
    this.useExisting = useExisting;
    this.useFactory = useFactory;
    this.deps = normalizeBlank(deps);
    this.multi = normalizeBool(multi);
  }

  static fromJson(data: {[key: string]: any}): CompileProviderMetadata {
    return new CompileProviderMetadata({
      token: _objFromJson(data['token'], CompileTokenMetadata.fromJson),
      useClass: _objFromJson(data['useClass'], CompileTypeMetadata.fromJson),
      useExisting: _objFromJson(data['useExisting'], CompileTokenMetadata.fromJson),
      useValue: _objFromJson(data['useValue'], CompileIdentifierMetadata.fromJson),
      useFactory: _objFromJson(data['useFactory'], CompileFactoryMetadata.fromJson),
      multi: data['multi'],
      deps: _arrayFromJson(data['deps'], CompileDiDependencyMetadata.fromJson)
    });
  }

  toJson(): {[key: string]: any} {
    return {
      // Note: Runtime type can't be serialized...
      'class': 'Provider',
      'token': _objToJson(this.token),
      'useClass': _objToJson(this.useClass),
      'useExisting': _objToJson(this.useExisting),
      'useValue': _objToJson(this.useValue),
      'useFactory': _objToJson(this.useFactory),
      'multi': this.multi,
      'deps': _arrayToJson(this.deps)
    };
  }
}

export class CompileFactoryMetadata implements CompileIdentifierMetadata,
    CompileMetadataWithIdentifier {
  runtime: Function;
  name: string;
  prefix: string;
  moduleUrl: string;
  value: any;
  diDeps: CompileDiDependencyMetadata[];

  constructor({runtime, name, moduleUrl, prefix, diDeps, value}: {
    runtime?: Function,
    name?: string,
    prefix?: string,
    moduleUrl?: string,
    value?: boolean,
    diDeps?: CompileDiDependencyMetadata[]
  }) {
    this.runtime = runtime;
    this.name = name;
    this.prefix = prefix;
    this.moduleUrl = moduleUrl;
    this.diDeps = _normalizeArray(diDeps);
    this.value = value;
  }

  get identifier(): CompileIdentifierMetadata { return this; }

  static fromJson(data: {[key: string]: any}): CompileFactoryMetadata {
    return new CompileFactoryMetadata({
      name: data['name'],
      prefix: data['prefix'],
      moduleUrl: data['moduleUrl'],
      value: data['value'],
      diDeps: _arrayFromJson(data['diDeps'], CompileDiDependencyMetadata.fromJson)
    });
  }

  toJson(): {[key: string]: any} {
    return {
      'class': 'Factory',
      'name': this.name,
      'prefix': this.prefix,
      'moduleUrl': this.moduleUrl,
      'value': this.value,
      'diDeps': _arrayToJson(this.diDeps)
    };
  }
}

export class CompileTokenMetadata implements CompileMetadataWithIdentifier {
  value: any;
  identifier: CompileIdentifierMetadata;
  identifierIsInstance: boolean;

  constructor({value, identifier, identifierIsInstance}: {
    value?: any,
    identifier?: CompileIdentifierMetadata,
    identifierIsInstance?: boolean
  }) {
    this.value = value;
    this.identifier = identifier;
    this.identifierIsInstance = normalizeBool(identifierIsInstance);
  }

  static fromJson(data: {[key: string]: any}): CompileTokenMetadata {
    return new CompileTokenMetadata({
      value: data['value'],
      identifier: _objFromJson(data['identifier'], CompileIdentifierMetadata.fromJson),
      identifierIsInstance: data['identifierIsInstance']
    });
  }

  toJson(): {[key: string]: any} {
    return {
      'value': this.value,
      'identifier': _objToJson(this.identifier),
      'identifierIsInstance': this.identifierIsInstance
    };
  }

  get runtimeCacheKey(): any {
    if (isPresent(this.identifier)) {
      return this.identifier.runtime;
    } else {
      return this.value;
    }
  }

  get assetCacheKey(): any {
    if (isPresent(this.identifier)) {
      return isPresent(this.identifier.moduleUrl) &&
                     isPresent(getUrlScheme(this.identifier.moduleUrl)) ?
                 `${this.identifier.name}|${this.identifier.moduleUrl}|${this.identifierIsInstance}` :
                 null;
    } else {
      return this.value;
    }
  }

  equalsTo(token2: CompileTokenMetadata): boolean {
    var rk = this.runtimeCacheKey;
    var ak = this.assetCacheKey;
    return (isPresent(rk) && rk == token2.runtimeCacheKey) ||
           (isPresent(ak) && ak == token2.assetCacheKey);
  }

  get name(): string { return isPresent(this.value) ? sanitizeIdentifier(this.value) : this.identifier.name; }
}

export class CompileTokenMap<VALUE> {
  private _valueMap = new Map<any, VALUE>();
  private _values: VALUE[] = [];

  add(token: CompileTokenMetadata, value: VALUE) {
    var existing = this.get(token);
    if (isPresent(existing)) {
      throw new BaseException(`Can only add to a TokenMap! Token: ${token.name}`);
    }
    this._values.push(value);
    var rk = token.runtimeCacheKey;
    if (isPresent(rk)) {
      this._valueMap.set(rk, value);
    }
    var ak = token.assetCacheKey;
    if (isPresent(ak)) {
      this._valueMap.set(ak, value);
    }
  }
  get(token: CompileTokenMetadata): VALUE {
    var rk = token.runtimeCacheKey;
    var ak = token.assetCacheKey;
    var result;
    if (isPresent(rk)) {
      result = this._valueMap.get(rk);
    }
    if (isBlank(result) && isPresent(ak)) {
      result = this._valueMap.get(ak);
    }
    return result;
  }
  values(): VALUE[] { return this._values; }
  get size(): number { return this._values.length; }
}

/**
 * Metadata regarding compilation of a type.
 */
export class CompileTypeMetadata implements CompileIdentifierMetadata, CompileMetadataWithType {
  runtime: Type;
  name: string;
  prefix: string;
  moduleUrl: string;
  isHost: boolean;
  value: any;
  diDeps: CompileDiDependencyMetadata[];

  constructor({runtime, name, moduleUrl, prefix, isHost, value, diDeps}: {
    runtime?: Type,
    name?: string,
    moduleUrl?: string,
    prefix?: string,
    isHost?: boolean,
    value?: any,
    diDeps?: CompileDiDependencyMetadata[]
  } = {}) {
    this.runtime = runtime;
    this.name = name;
    this.moduleUrl = moduleUrl;
    this.prefix = prefix;
    this.isHost = normalizeBool(isHost);
    this.value = value;
    this.diDeps = _normalizeArray(diDeps);
  }

  static fromJson(data: {[key: string]: any}): CompileTypeMetadata {
    return new CompileTypeMetadata({
      name: data['name'],
      moduleUrl: data['moduleUrl'],
      prefix: data['prefix'],
      isHost: data['isHost'],
      value: data['value'],
      diDeps: _arrayFromJson(data['diDeps'], CompileDiDependencyMetadata.fromJson)
    });
  }

  get identifier(): CompileIdentifierMetadata { return this; }
  get type(): CompileTypeMetadata { return this; }

  toJson(): {[key: string]: any} {
    return {
      // Note: Runtime type can't be serialized...
      'class': 'Type',
      'name': this.name,
      'moduleUrl': this.moduleUrl,
      'prefix': this.prefix,
      'isHost': this.isHost,
      'value': this.value,
      'diDeps': _arrayToJson(this.diDeps)
    };
  }
}

export class CompileQueryMetadata {
  selectors: Array<CompileTokenMetadata>;
  descendants: boolean;
  first: boolean;
  propertyName: string;
  read: CompileTokenMetadata;

  constructor({selectors, descendants, first, propertyName, read}: {
    selectors?: Array<CompileTokenMetadata>,
    descendants?: boolean,
    first?: boolean,
    propertyName?: string,
    read?: CompileTokenMetadata
  } = {}) {
    this.selectors = selectors;
    this.descendants = normalizeBool(descendants);
    this.first = normalizeBool(first);
    this.propertyName = propertyName;
    this.read = read;
  }

  static fromJson(data: {[key: string]: any}): CompileQueryMetadata {
    return new CompileQueryMetadata({
      selectors: _arrayFromJson(data['selectors'], CompileTokenMetadata.fromJson),
      descendants: data['descendants'],
      first: data['first'],
      propertyName: data['propertyName'],
      read: _objFromJson(data['read'], CompileTokenMetadata.fromJson)
    });
  }

  toJson(): {[key: string]: any} {
    return {
      'selectors': _arrayToJson(this.selectors),
      'descendants': this.descendants,
      'first': this.first,
      'propertyName': this.propertyName,
      'read': _objToJson(this.read)
    };
  }
}

/**
 * Metadata regarding compilation of a template.
 */
export class CompileTemplateMetadata {
  encapsulation: ViewEncapsulation;
  template: string;
  templateUrl: string;
  styles: string[];
  styleUrls: string[];
  ngContentSelectors: string[];
  baseUrl: string;
  constructor({encapsulation, template, templateUrl, styles, styleUrls, ngContentSelectors,
               baseUrl}: {
    encapsulation?: ViewEncapsulation,
    template?: string,
    templateUrl?: string,
    styles?: string[],
    styleUrls?: string[],
    ngContentSelectors?: string[],
    baseUrl?: string
  } = {}) {
    this.encapsulation = isPresent(encapsulation) ? encapsulation : ViewEncapsulation.Emulated;
    this.template = template;
    this.templateUrl = templateUrl;
    this.styles = isPresent(styles) ? styles : [];
    this.styleUrls = isPresent(styleUrls) ? styleUrls : [];
    this.ngContentSelectors = isPresent(ngContentSelectors) ? ngContentSelectors : [];
    this.baseUrl = baseUrl;
  }

  static fromJson(data: {[key: string]: any}): CompileTemplateMetadata {
    return new CompileTemplateMetadata({
      encapsulation: isPresent(data['encapsulation']) ?
                         VIEW_ENCAPSULATION_VALUES[data['encapsulation']] :
                         data['encapsulation'],
      template: data['template'],
      templateUrl: data['templateUrl'],
      styles: data['styles'],
      styleUrls: data['styleUrls'],
      ngContentSelectors: data['ngContentSelectors'],
      baseUrl: data['baseUrl']
    });
  }

  toJson(): {[key: string]: any} {
    return {
      'encapsulation':
          isPresent(this.encapsulation) ? serializeEnum(this.encapsulation) : this.encapsulation,
      'template': this.template,
      'templateUrl': this.templateUrl,
      'styles': this.styles,
      'styleUrls': this.styleUrls,
      'ngContentSelectors': this.ngContentSelectors,
      'baseUrl': this.baseUrl
    };
  }
}

/**
 * Metadata regarding compilation of a directive.
 */
export class CompileDirectiveMetadata implements CompileMetadataWithType {
  static create({type, isComponent, selector, exportAs, changeDetection, inputs, outputs, host,
                 lifecycleHooks, providers, viewProviders, queries, viewQueries, template}: {
    type?: CompileTypeMetadata,
    isComponent?: boolean,
    selector?: string,
    exportAs?: string,
    changeDetection?: ChangeDetectionStrategy,
    inputs?: string[],
    outputs?: string[],
    host?: {[key: string]: string},
    lifecycleHooks?: LifecycleHooks[],
    providers?:
        Array<CompileProviderMetadata | CompileTypeMetadata | CompileIdentifierMetadata | any[]>,
    viewProviders?:
        Array<CompileProviderMetadata | CompileTypeMetadata | CompileIdentifierMetadata | any[]>,
    queries?: CompileQueryMetadata[],
    viewQueries?: CompileQueryMetadata[],
    template?: CompileTemplateMetadata
  } = {}): CompileDirectiveMetadata {
    var hostListeners: {[key: string]: string} = {};
    var hostProperties: {[key: string]: string} = {};
    var hostAttributes: {[key: string]: string} = {};
    if (isPresent(host)) {
      StringMapWrapper.forEach(host, (value: string, key: string) => {
        var matches = RegExpWrapper.firstMatch(HOST_REG_EXP, key);
        if (isBlank(matches)) {
          hostAttributes[key] = value;
        } else if (isPresent(matches[1])) {
          hostProperties[matches[1]] = value;
        } else if (isPresent(matches[2])) {
          hostListeners[matches[2]] = value;
        }
      });
    }
    var inputsMap: {[key: string]: string} = {};
    if (isPresent(inputs)) {
      inputs.forEach((bindConfig: string) => {
        // canonical syntax: `dirProp: elProp`
        // if there is no `:`, use dirProp = elProp
        var parts = splitAtColon(bindConfig, [bindConfig, bindConfig]);
        inputsMap[parts[0]] = parts[1];
      });
    }
    var outputsMap: {[key: string]: string} = {};
    if (isPresent(outputs)) {
      outputs.forEach((bindConfig: string) => {
        // canonical syntax: `dirProp: elProp`
        // if there is no `:`, use dirProp = elProp
        var parts = splitAtColon(bindConfig, [bindConfig, bindConfig]);
        outputsMap[parts[0]] = parts[1];
      });
    }

    return new CompileDirectiveMetadata({
      type: type,
      isComponent: normalizeBool(isComponent),
      selector: selector,
      exportAs: exportAs,
      changeDetection: changeDetection,
      inputs: inputsMap,
      outputs: outputsMap,
      hostListeners: hostListeners,
      hostProperties: hostProperties,
      hostAttributes: hostAttributes,
      lifecycleHooks: isPresent(lifecycleHooks) ? lifecycleHooks : [],
      providers: providers,
      viewProviders: viewProviders,
      queries: queries,
      viewQueries: viewQueries,
      template: template
    });
  }
  type: CompileTypeMetadata;
  isComponent: boolean;
  selector: string;
  exportAs: string;
  changeDetection: ChangeDetectionStrategy;
  inputs: {[key: string]: string};
  outputs: {[key: string]: string};
  hostListeners: {[key: string]: string};
  hostProperties: {[key: string]: string};
  hostAttributes: {[key: string]: string};
  lifecycleHooks: LifecycleHooks[];
  providers: CompileProviderMetadata[];
  viewProviders: CompileProviderMetadata[];
  queries: CompileQueryMetadata[];
  viewQueries: CompileQueryMetadata[];

  template: CompileTemplateMetadata;
  constructor({type, isComponent, selector, exportAs, changeDetection, inputs, outputs,
               hostListeners, hostProperties, hostAttributes, lifecycleHooks, providers,
               viewProviders, queries, viewQueries, template}: {
    type?: CompileTypeMetadata,
    isComponent?: boolean,
    selector?: string,
    exportAs?: string,
    changeDetection?: ChangeDetectionStrategy,
    inputs?: {[key: string]: string},
    outputs?: {[key: string]: string},
    hostListeners?: {[key: string]: string},
    hostProperties?: {[key: string]: string},
    hostAttributes?: {[key: string]: string},
    lifecycleHooks?: LifecycleHooks[],
    providers?:
        Array<CompileProviderMetadata | CompileTypeMetadata | CompileIdentifierMetadata | any[]>,
    viewProviders?:
        Array<CompileProviderMetadata | CompileTypeMetadata | CompileIdentifierMetadata | any[]>,
    queries?: CompileQueryMetadata[],
    viewQueries?: CompileQueryMetadata[],
    template?: CompileTemplateMetadata
  } = {}) {
    this.type = type;
    this.isComponent = isComponent;
    this.selector = selector;
    this.exportAs = exportAs;
    this.changeDetection = changeDetection;
    this.inputs = inputs;
    this.outputs = outputs;
    this.hostListeners = hostListeners;
    this.hostProperties = hostProperties;
    this.hostAttributes = hostAttributes;
    this.lifecycleHooks = _normalizeArray(lifecycleHooks);
    this.providers = _normalizeArray(providers);
    this.viewProviders = _normalizeArray(viewProviders);
    this.queries = _normalizeArray(queries);
    this.viewQueries = _normalizeArray(viewQueries);
    this.template = template;
  }

  get identifier(): CompileIdentifierMetadata { return this.type; }

  static fromJson(data: {[key: string]: any}): CompileDirectiveMetadata {
    return new CompileDirectiveMetadata({
      isComponent: data['isComponent'],
      selector: data['selector'],
      exportAs: data['exportAs'],
      type: isPresent(data['type']) ? CompileTypeMetadata.fromJson(data['type']) : data['type'],
      changeDetection: isPresent(data['changeDetection']) ?
                           CHANGE_DETECTION_STRATEGY_VALUES[data['changeDetection']] :
                           data['changeDetection'],
      inputs: data['inputs'],
      outputs: data['outputs'],
      hostListeners: data['hostListeners'],
      hostProperties: data['hostProperties'],
      hostAttributes: data['hostAttributes'],
      lifecycleHooks:
          (<any[]>data['lifecycleHooks']).map(hookValue => LIFECYCLE_HOOKS_VALUES[hookValue]),
      template: isPresent(data['template']) ? CompileTemplateMetadata.fromJson(data['template']) :
                                              data['template'],
      providers: _arrayFromJson(data['providers'], metadataFromJson),
      viewProviders: _arrayFromJson(data['viewProviders'], metadataFromJson),
      queries: _arrayFromJson(data['queries'], CompileQueryMetadata.fromJson),
      viewQueries: _arrayFromJson(data['viewQueries'], CompileQueryMetadata.fromJson)
    });
  }

  toJson(): {[key: string]: any} {
    return {
      'class': 'Directive',
      'isComponent': this.isComponent,
      'selector': this.selector,
      'exportAs': this.exportAs,
      'type': isPresent(this.type) ? this.type.toJson() : this.type,
      'changeDetection': isPresent(this.changeDetection) ? serializeEnum(this.changeDetection) :
                                                           this.changeDetection,
      'inputs': this.inputs,
      'outputs': this.outputs,
      'hostListeners': this.hostListeners,
      'hostProperties': this.hostProperties,
      'hostAttributes': this.hostAttributes,
      'lifecycleHooks': this.lifecycleHooks.map(hook => serializeEnum(hook)),
      'template': isPresent(this.template) ? this.template.toJson() : this.template,
      'providers': _arrayToJson(this.providers),
      'viewProviders': _arrayToJson(this.viewProviders),
      'queries': _arrayToJson(this.queries),
      'viewQueries': _arrayToJson(this.viewQueries)
    };
  }
}

/**
 * Construct {@link CompileDirectiveMetadata} from {@link ComponentTypeMetadata} and a selector.
 */
export function createHostComponentMeta(componentType: CompileTypeMetadata,
                                        componentSelector: string): CompileDirectiveMetadata {
  var template = CssSelector.parse(componentSelector)[0].getMatchingElementTemplate();
  return CompileDirectiveMetadata.create({
    type: new CompileTypeMetadata({
      runtime: Object,
      name: `${componentType.name}_Host`,
      moduleUrl: componentType.moduleUrl,
      isHost: true
    }),
    template: new CompileTemplateMetadata(
        {template: template, templateUrl: '', styles: [], styleUrls: [], ngContentSelectors: []}),
    changeDetection: ChangeDetectionStrategy.Default,
    inputs: [],
    outputs: [],
    host: {},
    lifecycleHooks: [],
    isComponent: true,
    selector: '*',
    providers: [],
    viewProviders: [],
    queries: [],
    viewQueries: []
  });
}


export class CompilePipeMetadata implements CompileMetadataWithType {
  type: CompileTypeMetadata;
  name: string;
  pure: boolean;
  lifecycleHooks: LifecycleHooks[];

  constructor({type, name, pure, lifecycleHooks}: {
    type?: CompileTypeMetadata,
    name?: string,
    pure?: boolean,
    lifecycleHooks?: LifecycleHooks[]
  } = {}) {
    this.type = type;
    this.name = name;
    this.pure = normalizeBool(pure);
    this.lifecycleHooks = _normalizeArray(lifecycleHooks);
  }
  get identifier(): CompileIdentifierMetadata { return this.type; }

  static fromJson(data: {[key: string]: any}): CompilePipeMetadata {
    return new CompilePipeMetadata({
      type: isPresent(data['type']) ? CompileTypeMetadata.fromJson(data['type']) : data['type'],
      name: data['name'],
      pure: data['pure']
    });
  }

  toJson(): {[key: string]: any} {
    return {
      'class': 'Pipe',
      'type': isPresent(this.type) ? this.type.toJson() : null,
      'name': this.name,
      'pure': this.pure
    };
  }
}

var _COMPILE_METADATA_FROM_JSON = {
  'Directive': CompileDirectiveMetadata.fromJson,
  'Pipe': CompilePipeMetadata.fromJson,
  'Type': CompileTypeMetadata.fromJson,
  'Provider': CompileProviderMetadata.fromJson,
  'Identifier': CompileIdentifierMetadata.fromJson,
  'Factory': CompileFactoryMetadata.fromJson
};

function _arrayFromJson(obj: any[], fn: (a: {[key: string]: any}) => any): any {
  return isBlank(obj) ? null : obj.map(o => _objFromJson(o, fn));
}

function _arrayToJson(obj: any[]): string | {[key: string]: any} {
  return isBlank(obj) ? null : obj.map(_objToJson);
}

function _objFromJson(obj: any, fn: (a: {[key: string]: any}) => any): any {
  if (isArray(obj)) return _arrayFromJson(obj, fn);
  if (isString(obj) || isBlank(obj) || isBoolean(obj) || isNumber(obj)) return obj;
  return fn(obj);
}

function _objToJson(obj: any): string | {[key: string]: any} {
  if (isArray(obj)) return _arrayToJson(obj);
  if (isString(obj) || isBlank(obj) || isBoolean(obj) || isNumber(obj)) return obj;
  return obj.toJson();
}

function _normalizeArray(obj: any[]): any[] {
  return isPresent(obj) ? obj : [];
}