// 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 'package:analysis_server/protocol/protocol_generated.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';

import '../support/integration_tests.dart';

void main() {
  defineReflectiveSuite(() {
    defineReflectiveTests(AnalysisNavigationTest);
  });
}

@reflectiveTest
class AnalysisNavigationTest extends AbstractAnalysisServerIntegrationTest {
  Future<void> test_navigation() async {
    var pathname1 = sourcePath('test1.dart');
    var text1 = r'''
library foo;

import 'dart:async';
part 'test2.dart';

class Class<TypeParameter> {
  Class.constructor(); /* constructor declaration */

  TypeParameter field = (throw 0);

  method() {}
}

typedef FunctionTypeAlias();

function(FunctionTypeAlias parameter) {
  print(parameter());
}

int topLevelVariable = 0;

void f() {
  Class<int> localVariable = new Class<int>.constructor(); // usage
  function(() => localVariable.field);
  localVariable.method();
  localVariable.field = 1;
}
''';
    writeFile(pathname1, text1);
    var pathname2 = sourcePath('test2.dart');
    var text2 = r'''
part of foo;
''';
    writeFile(pathname2, text2);
    await standardAnalysisSetup();
    await sendAnalysisSetSubscriptions({
      AnalysisService.NAVIGATION: [pathname1],
    });

    // There should be a single error, due to the fact that 'dart:async' is not
    // used.
    await analysisFinished;
    expect(currentAnalysisErrors[pathname1], hasLength(1));
    expect(currentAnalysisErrors[pathname2], isEmpty);

    var params = await onAnalysisNavigation.first;
    expect(params.file, equals(pathname1));
    var regions = params.regions;
    var targets = params.targets;
    var targetFiles = params.files;

    NavigationTarget findTargetElement(int index) {
      for (var region in regions) {
        if (region.offset <= index && index < region.offset + region.length) {
          expect(region.targets, hasLength(1));
          var targetIndex = region.targets[0];
          return targets[targetIndex];
        }
      }
      fail('No element found for index $index');
    }

    void checkLocal(
      String source,
      String expectedTarget,
      ElementKind expectedKind,
    ) {
      var sourceIndex = text1.indexOf(source);
      var targetIndex = text1.indexOf(expectedTarget);
      var element = findTargetElement(sourceIndex);
      expect(targetFiles[element.fileIndex], equals(pathname1));
      expect(element.offset, equals(targetIndex));
      expect(element.kind, equals(expectedKind));
    }

    void checkRemote(
      String source,
      String expectedTargetRegexp,
      ElementKind expectedKind,
    ) {
      var sourceIndex = text1.indexOf(source);
      var element = findTargetElement(sourceIndex);
      expect(targetFiles[element.fileIndex], matches(expectedTargetRegexp));
      expect(element.kind, equals(expectedKind));
    }

    // TODO(paulberry): will the element type 'CLASS_TYPE_ALIAS' ever appear as
    // a navigation target?
    checkLocal('Class<int>', 'Class<TypeParameter>', ElementKind.CLASS);
    checkRemote("'test2.dart';", r'test2.dart$', ElementKind.COMPILATION_UNIT);
    checkLocal(
      'Class<int>.constructor',
      'Class<TypeParameter>',
      ElementKind.CLASS,
    );
    checkLocal(
      'constructor(); // usage',
      'constructor(); /* constructor declaration */',
      ElementKind.CONSTRUCTOR,
    );
    checkLocal('field = (', 'field = (', ElementKind.FIELD);
    checkLocal(
      'function(() => localVariable.field)',
      'function(FunctionTypeAlias parameter)',
      ElementKind.FUNCTION,
    );
    checkLocal(
      'FunctionTypeAlias parameter',
      'FunctionTypeAlias();',
      ElementKind.TYPE_ALIAS,
    );
    checkLocal('field)', 'field = (', ElementKind.FIELD);
    checkRemote("'dart:async'", r'async\.dart$', ElementKind.LIBRARY);
    checkLocal(
      'localVariable.field',
      'localVariable =',
      ElementKind.LOCAL_VARIABLE,
    );
    checkLocal('method();', 'method() {', ElementKind.METHOD);
    checkLocal('parameter());', 'parameter) {', ElementKind.PARAMETER);
    checkLocal('field = 1', 'field = (', ElementKind.FIELD);
    checkLocal(
      'topLevelVariable = 0;',
      'topLevelVariable = 0;',
      ElementKind.TOP_LEVEL_VARIABLE,
    );
    checkLocal(
      'TypeParameter field = (',
      'TypeParameter>',
      ElementKind.TYPE_PARAMETER,
    );
  }
}
