// Copyright (c) 2018, 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 'package:analyzer_utilities/html_dom.dart';
import 'package:analyzer_utilities/html_generator.dart';
import 'package:analyzer_utilities/tools.dart';

import 'api.dart';
import 'codegen_dart.dart';
import 'codegen_protocol_constants.dart' show generateConstName;
import 'from_html.dart';

GeneratedFile clientTarget() {
  return GeneratedFile(
    'analysis_server_client/lib/handler/notification_handler.dart',
    (pkgRoot) async {
      var visitor = CodegenNotificationHandlerVisitor(readApi(pkgRoot));
      return visitor.collectCode(visitor.visitApi);
    },
  );
}

String _capitalize(String name) =>
    '${name.substring(0, 1).toUpperCase()}${name.substring(1)}';

List<String> _generateDartDoc(Element html) => html.children
    .where((Element elem) => elem.name == 'p')
    .map<String>((Element elem) => innerText(elem).trim())
    .toList();

String _generateNotificationMethodName(String domainName, String event) =>
    'on${_capitalize(domainName)}${_capitalize(event)}';

String _generateParamTypeName(String domainName, String event) =>
    '${_capitalize(domainName)}${_capitalize(event)}Params';

/// Visitor which produces Dart code representing the API.
class CodegenNotificationHandlerVisitor extends DartCodegenVisitor
    with CodeGenerator {
  new(super.api) {
    codeGeneratorSettings.commentLineLength = 79;
    codeGeneratorSettings.docCommentStartMarker = null;
    codeGeneratorSettings.docCommentLineLeader = '/// ';
    codeGeneratorSettings.docCommentEndMarker = null;
    codeGeneratorSettings.languageName = 'dart';
  }

  void emitDartdoc(List<String> dartdoc) {
    var first = true;
    for (var paragraph in dartdoc) {
      if (first) {
        first = false;
      } else {
        writeln('  ///');
      }
      for (var line in paragraph.split(RegExp('\r?\n'))) {
        writeln('  /// ${line.trim()}');
      }
    }
  }

  void emitImports() {
    writeln("import 'package:analysis_server_client/protocol.dart';");
  }

  void emitNotificationHandler() {
    var visitor = _NotificationVisitor(api)..visitApi();
    var notifications = visitor.notificationConstants;
    notifications.sort((n1, n2) => n1.constName.compareTo(n2.constName));

    writeln('''
/// [NotificationHandler] processes analysis server notifications
/// and dispatches those notifications to different methods based upon
/// the type of notification. Clients may override
/// any of the "on<EventName>" methods that are of interest.
///
/// Clients may mix-in this class, but may not implement it.
mixin NotificationHandler {
  void handleEvent(Notification notification) {
    var params = notification.params;
    var decoder = ResponseDecoder(null);
    switch (notification.event) {
''');
    for (var notification in notifications) {
      writeln('      case ${notification.constName}:');
      writeln('        ${notification.methodName}(');
      writeln('          ${notification.paramsTypeName}');
      writeln("            .fromJson(decoder, 'params', params));");
      writeln('        break;');
    }
    writeln('      default:');
    writeln('        onUnknownNotification(notification.event, params);');
    writeln('        break;');
    writeln('    }');
    writeln('  }');
    for (var notification in notifications) {
      writeln();
      emitDartdoc(notification.dartdoc);
      writeln('  void ${notification.methodName}(');
      writeln('    ${notification.paramsTypeName} params) {');
      writeln('  }');
    }
    writeln();
    writeln('  /// Reports a notification that is not processed');
    writeln('  /// by any other notification handlers.');
    writeln('  void onUnknownNotification(String event, params) {}');
    writeln('}');
  }

  @override
  void visitApi() {
    outputHeader(year: '2018');
    writeln();
    emitImports();
    emitNotificationHandler();
  }
}

class _Notification {
  final String constName;
  final String methodName;
  final String paramsTypeName;
  final List<String> dartdoc;

  new(this.constName, this.methodName, this.paramsTypeName, this.dartdoc);
}

class _NotificationVisitor extends HierarchicalApiVisitor {
  final notificationConstants = <_Notification>[];

  new(super.api);

  @override
  void visitNotification(Notification notification) {
    notificationConstants.add(
      _Notification(
        generateConstName(
          notification.domainName,
          'notification',
          notification.event,
        ),
        _generateNotificationMethodName(
          notification.domainName,
          notification.event,
        ),
        _generateParamTypeName(notification.domainName, notification.event),
        _generateDartDoc(notification.html!),
      ),
    );
  }
}
