// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'error.dart';

/// An object sent from the Flutter Driver to a Flutter application to instruct
/// the application to perform a task.
abstract class Command {
  /// Identifies the type of the command object and of the handler.
  String get kind;

  /// Serializes this command to parameter name/value pairs.
  Map<String, String> serialize();
}

/// An object sent from a Flutter application back to the Flutter Driver in
/// response to a command.
abstract class Result {
  /// Serializes this message to a JSON map.
  Map<String, dynamic> toJson();
}

/// A serializable reference to an object that lives in the application isolate.
class ObjectRef extends Result {
  ObjectRef(this.objectReferenceKey);

  ObjectRef.notFound() : this(null);

  static ObjectRef fromJson(Map<String, dynamic> json) {
    return json['objectReferenceKey'] != null
      ? new ObjectRef(json['objectReferenceKey'])
      : null;
  }

  /// Identifier used to dereference an object.
  ///
  /// This value is generated by the application-side isolate. Flutter driver
  /// tests should not generate these keys.
  final String objectReferenceKey;

  Map<String, dynamic> toJson() => {
    'objectReferenceKey': objectReferenceKey,
  };
}

/// A command aimed at an object represented by [targetRef].
///
/// Implementations must provide a concrete [kind]. If additional data is
/// required beyond the [targetRef] the implementation may override [serialize]
/// and add more keys to the returned map.
abstract class CommandWithTarget extends Command {
  CommandWithTarget(ObjectRef ref) : this.targetRef = ref?.objectReferenceKey {
    if (ref == null)
      throw new DriverError('${this.runtimeType} target cannot be null');

    if (ref.objectReferenceKey == null)
      throw new DriverError('${this.runtimeType} target reference cannot be null');
  }

  /// Refers to the object targeted by this command.
  final String targetRef;

  /// This method is meant to be overridden if data in addition to [targetRef]
  /// is serialized to JSON.
  ///
  /// Example:
  ///
  ///     Map<String, String> toJson() => super.toJson()..addAll({
  ///       'foo': this.foo,
  ///     });
  Map<String, String> serialize() => <String, String>{
    'targetRef': targetRef,
  };
}