// Copyright (c) 2017, 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:convert';
import 'dart:io';

import 'package:analysis_server/src/status/diagnostics.dart';
import 'package:analysis_server/src/status/pages.dart';
import 'package:analysis_server_plugin/src/correction/performance.dart';
import 'package:collection/collection.dart';

class TimingPage extends DiagnosticPageWithNav with PerformanceChartMixin {
  new(DiagnosticsSite site)
    : super(site, 'timing', 'Timing', description: 'Timing statistics.');

  @override
  ContentType contentType(Map<String, String> params) {
    if (params['asJson'] != null) {
      return ContentType.json;
    }
    return super.contentType(params);
  }

  @override
  Future<void> generateContent(Map<String, String> params) async {
    var kind = params['kind'];

    List<RequestPerformance> items;
    List<RequestPerformance>? itemsSlow;
    if (kind == 'completion') {
      items = server.recentPerformance.completion.items.toList();
    } else if (kind == 'getAssists') {
      items = server.recentPerformance.getAssists.items.toList();
    } else if (kind == 'getFixes') {
      items = server.recentPerformance.getFixes.items.toList();
    } else if (kind == 'getRefactorings') {
      items = server.recentPerformance.getRefactorings.items.toList();
    } else {
      items = server.recentPerformance.requests.items.toList();
      itemsSlow = server.recentPerformance.slowRequests.items.toList();
    }

    var id = int.tryParse(params['id'] ?? '');
    if (id == null) {
      _generateList(items, itemsSlow);
    } else {
      _generateDetails(id, items, itemsSlow);
    }
  }

  @override
  Future<void> generatePage(Map<String, String> params) async {
    if (params['asJson'] != null) {
      var data = [];
      // No added header etc.
      for (var item in server.recentPerformance.requests.items.toList()) {
        data.add({
          'id': item.id,
          'operation': item.operation,
          'elapsedMs': item.performance.elapsed.inMilliseconds,
          'latency': item.requestLatency,
          'performance': item.performance.toJson(),
        });
      }
      buf.write(json.encode(data));
      return;
    }
    return await super.generatePage(params);
  }

  void _emitTable(List<RequestPerformance> items) {
    buf.writeln('<table>');
    buf.writeln('<tr><th>Time</th><th>Request</th></tr>');
    for (var item in items) {
      buf.writeln(
        '<tr>'
        '<td class="pre right"><a href="timing?id=${item.id}">'
        '${formatLatencyTiming(item.performance.elapsed.inMilliseconds, item.requestLatency)}'
        '</a></td>'
        '<td>${escape(item.operation)}</td>'
        '</tr>',
      );
    }
    buf.writeln('</table>');
  }

  void _generateDetails(
    int id,
    List<RequestPerformance> items,
    List<RequestPerformance>? itemsSlow,
  ) {
    var item = items.firstWhereOrNull((info) => info.id == id);
    if (item == null && itemsSlow != null) {
      item = itemsSlow.firstWhereOrNull((info) => info.id == id);
    }

    if (item == null) {
      blankslate(
        'Unable to find data for $id. '
        'Perhaps newer requests have pushed it out of the buffer?',
      );
      return;
    }

    h3("Request '${item.operation}'");
    var requestLatency = item.requestLatency;
    if (requestLatency != null) {
      buf.writeln('Request latency: $requestLatency ms.');
      buf.writeln('<p>');
    }
    var startTime = item.startTime;
    if (startTime != null) {
      buf.writeln('Request start time: ${startTime.toIso8601String()}.');
      buf.writeln('<p>');
    }
    var buffer = StringBuffer();
    item.performance.write(buffer: buffer);
    pre(() {
      buf.write('<code>');
      buf.write(escape('$buffer'));
      buf.writeln('</code>');
    });

    if (item is ProducerRequestPerformance) {
      var itemTimings = item.producerTimings;
      if (itemTimings.isNotEmpty) {
        h3('Producer Timings');
        buf.writeln('<table>');
        buf.writeln('<tr><th>Time (ms)</th><th>Producer</th></tr>');
        for (var timing in itemTimings) {
          buf.writeln(
            '<tr>'
            '<td class="right">${timing.elapsedTime}</td>'
            '<td>${escape(timing.className)}</td>'
            '</tr>',
          );
        }
        buf.writeln('</table>');
      }

      var snippet = item.snippet;
      if (snippet.isNotEmpty) {
        h3('Snippet');
        pre(() {
          buf.writeln(escape(snippet));
        });
      }
    }
  }

  void _generateList(
    List<RequestPerformance> items,
    List<RequestPerformance>? itemsSlow,
  ) {
    if (items.isEmpty) {
      assert(itemsSlow == null || itemsSlow.isEmpty);
      blankslate('No requests recorded.');
      return;
    }

    drawChart(items);

    // emit the data as a table
    if (itemsSlow != null) {
      h3('Recent requests');
    }
    _emitTable(items);

    if (itemsSlow != null) {
      h3('Slow requests');
      _emitTable(itemsSlow);
    }
  }
}
