// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:async';
import 'dart:isolate';

import 'package:barback/barback.dart';
// TODO(nweiz): don't import from "src" once issue 14966 is fixed.
import 'package:barback/src/internal_asset.dart';

import '../serialize.dart';
import 'get_input_transform.dart';

/// Serialize the methods shared between [AggregateTransform] and
/// [DeclaringAggregateTransform].
///
/// [additionalFields] contains additional serialized fields to add to the
/// serialized transform. [methodHandlers] is a set of additional methods. Each
/// value should take a JSON message and return the response (which may be a
/// Future).
Map _serializeBaseAggregateTransform(
    transform,
    Map<String, dynamic> additionalFields,
    Map<String, Function> methodHandlers) {
  var receivePort = new ReceivePort();
  receivePort.listen((wrappedMessage) {
    respond(wrappedMessage, (message) {
      var handler = methodHandlers[message['type']];
      if (handler != null) return handler(message);

      if (message['type'] == 'consumePrimary') {
        transform.consumePrimary(deserializeId(message['assetId']));
        return null;
      }

      assert(message['type'] == 'log');
      var method = {
        'Info': transform.logger.info,
        'Fine': transform.logger.fine,
        'Warning': transform.logger.warning,
        'Error': transform.logger.error
      }[message['level']];
      assert(method != null);

      var assetId =
          message['assetId'] == null ? null : deserializeId(message['assetId']);
      var span =
          message['span'] == null ? null : deserializeSpan(message['span']);
      method(message['message'], asset: assetId, span: span);
    });
  });

  return {
    'port': receivePort.sendPort,
    'key': transform.key,
    'package': transform.package
  }..addAll(additionalFields);
}

/// Converts [transform] into a serializable map.
Map serializeAggregateTransform(AggregateTransform transform) {
  return _serializeBaseAggregateTransform(transform, {
    'primaryInputs': serializeStream(transform.primaryInputs, serializeAsset)
  }, {
    'getInput': (message) => transform
        .getInput(deserializeId(message['id']))
        .then((asset) => serializeAsset(asset)),
    'addOutput': (message) =>
        transform.addOutput(deserializeAsset(message['output']))
  });
}

/// Converts [transform] into a serializable map.
Map serializeDeclaringAggregateTransform(
    DeclaringAggregateTransform transform) {
  return _serializeBaseAggregateTransform(transform, {
    'primaryIds': serializeStream(transform.primaryIds, serializeId)
  }, {
    'declareOutput': (message) =>
        transform.declareOutput(deserializeId(message['output']))
  });
}

/// The base class for wrappers for [AggregateTransform]s that are in the host
/// isolate.
class _ForeignBaseAggregateTransform {
  /// The port with which we communicate with the host isolate.
  ///
  /// This port and all messages sent across it are specific to this transform.
  final SendPort _port;

  final String key;

  final String package;

  TransformLogger get logger => _logger;
  TransformLogger _logger;

  _ForeignBaseAggregateTransform(Map transform)
      : _port = transform['port'],
        key = transform['key'],
        package = transform['package'] {
    _logger = new TransformLogger((assetId, level, message, span) {
      call(_port, {
        'type': 'log',
        'level': level.name,
        'message': message,
        'assetId': assetId == null ? null : serializeId(assetId),
        'span': span == null ? null : serializeSpan(span)
      });
    });
  }

  void consumePrimary(AssetId id) {
    call(_port, {'type': 'consumePrimary', 'assetId': serializeId(id)});
  }
}

// We can get away with only removing the class declarations in incompatible
// barback versions because merely referencing undefined types in type
// annotations isn't a static error. Only implementing an undefined interface is
// a static error.

/// A wrapper for an [AggregateTransform] that's in the host isolate.
///
/// This retrieves inputs from and sends outputs and logs to the host isolate.
class ForeignAggregateTransform extends _ForeignBaseAggregateTransform
    with GetInputTransform
    implements AggregateTransform {
  final Stream<Asset> primaryInputs;

  /// Creates a transform from a serialized map sent from the host isolate.
  ForeignAggregateTransform(Map transform)
      : primaryInputs =
            deserializeStream(transform['primaryInputs'], deserializeAsset),
        super(transform);

  Future<Asset> getInput(AssetId id) {
    return call(_port, {'type': 'getInput', 'id': serializeId(id)})
        .then(deserializeAsset);
  }

  void addOutput(Asset output) {
    call(_port, {'type': 'addOutput', 'output': serializeAsset(output)});
  }
}

/// A wrapper for a [DeclaringAggregateTransform] that's in the host isolate.
class ForeignDeclaringAggregateTransform extends _ForeignBaseAggregateTransform
    implements DeclaringAggregateTransform {
  final Stream<AssetId> primaryIds;

  /// Creates a transform from a serializable map sent from the host isolate.
  ForeignDeclaringAggregateTransform(Map transform)
      : primaryIds = deserializeStream(transform['primaryIds'], deserializeId),
        super(transform);

  void declareOutput(AssetId id) {
    call(_port, {'type': 'declareOutput', 'output': serializeId(id)});
  }
}
