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

class L10nException implements Exception {
  L10nException(this.message);

  final String message;
}

class OptionalParameter {
  const OptionalParameter(this.name, this.value) : assert(name != null), assert(value != null);

  final String name;
  final Object value;
}

class Placeholder {
  Placeholder(this.resourceId, this.name, Map<String, dynamic> attributes)
    : assert(resourceId != null),
      assert(name != null),
      example = _stringAttribute(resourceId, name, attributes, 'example'),
      type = _stringAttribute(resourceId, name, attributes, 'type') ?? 'Object',
      format = _stringAttribute(resourceId, name, attributes, 'format'),
      optionalParameters = _optionalParameters(resourceId, name, attributes);

  final String resourceId;
  final String name;
  final String example;
  final String type;
  final String format;
  final List<OptionalParameter> optionalParameters;

  bool get requiresFormatting => <String>['DateTime', 'double', 'int', 'num'].contains(type);
  bool get isNumber => <String>['double', 'int', 'num'].contains(type);
  bool get isDate => 'DateTime' == type;

  static String _stringAttribute(
    String resourceId,
    String name,
    Map<String, dynamic> attributes,
    String attributeName,
  ) {
    final dynamic value = attributes[attributeName];
    if (value == null)
      return null;
    if (value is! String || (value as String).isEmpty) {
      throw L10nException(
        'The "$attributeName" value of the "$name" placeholder in message $resourceId '
        'must be a non-empty string.',
      );
    }
    return value as String;
  }

  static List<OptionalParameter> _optionalParameters(
    String resourceId,
    String name,
    Map<String, dynamic> attributes
  ) {
    final dynamic value = attributes['optionalParameters'];
    if (value == null)
      return <OptionalParameter>[];
    if (value is! Map<String, Object>) {
      throw L10nException(
        'The "optionalParameters" value of the "$name" placeholder in message '
        '$resourceId is not a properly formatted Map. Ensure that it is a map '
        'with keys that are strings.'
      );
    }
    final Map<String, dynamic> optionalParameterMap = value as Map<String, dynamic>;
    return optionalParameterMap.keys.map<OptionalParameter>((String parameterName) {
      return OptionalParameter(parameterName, optionalParameterMap[parameterName]);
    }).toList();
  }
}

class Message {
  Message(Map<String, dynamic> bundle, this.resourceId)
    : assert(bundle != null),
      assert(resourceId != null && resourceId.isNotEmpty),
      value = _value(bundle, resourceId),
      description = _description(bundle, resourceId),
      placeholders = _placeholders(bundle, resourceId);

  final String resourceId;
  final String value;
  final String description;
  final List<Placeholder> placeholders;

  bool get placeholdersRequireFormatting => placeholders.any((Placeholder p) => p.requiresFormatting);

  static String _value(Map<String, dynamic> bundle, String resourceId) {
    final dynamic value = bundle[resourceId];
    if (value == null)
      throw L10nException('A value for resource "$resourceId" was not found.');
    if (value is! String)
      throw L10nException('The value of "$resourceId" is not a string.');
    return bundle[resourceId] as String;
  }

  static Map<String, dynamic> _attributes(Map<String, dynamic> bundle, String resourceId) {
    final dynamic attributes = bundle['@$resourceId'];
    if (attributes == null) {
      throw L10nException(
        'Resource attribute "@$resourceId" was not found. Please '
        'ensure that each resource has a corresponding @resource.'
      );
    }
    if (attributes is! Map<String, dynamic>) {
      throw L10nException(
        'The resource attribute "@$resourceId" is not a properly formatted Map. '
        'Ensure that it is a map with keys that are strings.'
      );
    }
    return attributes as Map<String, dynamic>;
  }

  static String _description(Map<String, dynamic> bundle, String resourceId) {
    final dynamic value = _attributes(bundle, resourceId)['description'];
    if (value == null)
      return null;
    if (value is! String) {
      throw L10nException(
        'The description for "@$resourceId" is not a properly formatted String.'
      );
    }
    return value as String;
  }

  static List<Placeholder> _placeholders(Map<String, dynamic> bundle, String resourceId) {
    final dynamic value = _attributes(bundle, resourceId)['placeholders'];
    if (value == null)
      return <Placeholder>[];
    if (value is! Map<String, dynamic>) {
      throw L10nException(
        'The "placeholders" attribute for message $resourceId, is not '
        'properly formatted. Ensure that it is a map with string valued keys.'
      );
    }
    final Map<String, dynamic> allPlaceholdersMap = value as Map<String, dynamic>;
    return allPlaceholdersMap.keys.map<Placeholder>((String placeholderName) {
      final dynamic value = allPlaceholdersMap[placeholderName];
      if (value is! Map<String, dynamic>) {
        throw L10nException(
          'The value of the "$placeholderName" placeholder attribute for message '
          '"$resourceId", is not properly formatted. Ensure that it is a map '
          'with string valued keys.'
        );
      }
      return Placeholder(resourceId, placeholderName, value as Map<String, dynamic>);
    }).toList();
  }
}
