import 'dart:async';

import 'package:angular2/src/transform/common/asset_reader.dart';
import 'package:angular2/src/transform/common/names.dart';
import 'package:angular2/src/transform/common/zone.dart' as zone;
import 'package:barback/barback.dart';

import 'rewriter.dart';

/// Transformer which rewrites deferred imports and `loadLibrary` calls.
///
/// Deferred imports with associated initialization code are rewritten to
/// point to their associated generated code file.
/// `loadLibrary` calls for libraries with associated initialization methods
/// are rewritten to initialize after the library is loaded and before use.
class DeferredRewriter extends AggregateTransformer implements LazyTransformer {
  DeferredRewriter();

  /// Ctor which tells pub that this can be run as a standalone transformer.
  DeferredRewriter.asPlugin(BarbackSettings _);

  @override
  declareOutputs(DeclaringTransform transform) {
    transform.declareOutput(transform.primaryId);
  }

  @override
  dynamic classifyPrimary(AssetId id) {
    // Map <name>.dart and <name>.dart.deferredCount => <name>.
    // Anything else to `null`.
    var extension;
    if (id.path.endsWith(DEFERRED_EXTENSION)) {
      extension = DEFERRED_EXTENSION;
    } else if (id.path.endsWith('.dart') && !isGenerated(id.path)) {
      extension = '.dart';
    } else {
      return null;
    }
    return id.path.substring(0, id.path.length - extension.length);
  }

  @override
  Future apply(AggregateTransform transform) async {
    final dartAsset = await _assetToProcess(transform);
    if (dartAsset == null) return null;
    return applyImpl(transform, dartAsset.id);
  }

  /// Transforms the asset with id [id].
  ///
  /// [transform] must be an [AggregateTransform] or [Transform].
  Future applyImpl(dynamic transform, AssetId id) {
    assert(transform is AggregateTransform || transform is Transform);

    return zone.exec(() async {
      var transformedCode = await rewriteDeferredLibraries(
          new AssetReader.fromTransform(transform), id);
      if (transformedCode != null) {
        transform.addOutput(new Asset.fromString(id, transformedCode));
      }
    }, log: transform.logger);
  }
}

/// Returns the asset we need to process or `null` if none exists.
///
/// Consumes the .dart.deferredCount asset if it is present.
Future<Asset> _assetToProcess(AggregateTransform transform) async {
  // We only need to process .dart files that have an associated
  // .dart.deferredCount file with a value != "0".
  //
  // The .dart.deferredCount file is generated by a previous phase for files
  // which have deferred imports. An absent .dart.deferredCount asset is the
  // treated the same as a .dart.deferredCount asset with value "0".
  var dartAsset, deferredCountAsset;
  await for (Asset a in transform.primaryInputs) {
    if (a.id.path.endsWith(DEFERRED_EXTENSION)) {
      deferredCountAsset = a;
    } else if (a.id.path.endsWith('.dart')) {
      dartAsset = a;
    }
  }
  if (deferredCountAsset == null) return null;
  // No longer necessary.
  transform.consumePrimary(deferredCountAsset.id);
  if ((await deferredCountAsset.readAsString()) == "0") return null;
  return dartAsset;
}

Future<String> rewriteDeferredLibraries(AssetReader reader, AssetId id) =>
    rewriteLibrary(id, reader);
