// Copyright (c) 2016, 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 'package:analysis_server/src/services/search/search_engine.dart';
import 'package:analysis_server/src/services/search/search_engine_internal2.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/file_system/memory_file_system.dart';
import 'package:analyzer/src/dart/analysis/driver.dart';
import 'package:analyzer/src/dart/analysis/file_state.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/sdk.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:front_end/src/base/performace_logger.dart';
import 'package:front_end/src/incremental/byte_store.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';

import '../../mock_sdk.dart';

main() {
  defineReflectiveSuite(() {
    defineReflectiveTests(SearchEngineImpl2Test);
  });
}

@reflectiveTest
class SearchEngineImpl2Test {
  final MemoryResourceProvider provider = new MemoryResourceProvider();
  DartSdk sdk;
  final ByteStore byteStore = new MemoryByteStore();
  final FileContentOverlay contentOverlay = new FileContentOverlay();

  final StringBuffer logBuffer = new StringBuffer();
  PerformanceLog logger;

  AnalysisDriverScheduler scheduler;

  void setUp() {
    sdk = new MockSdk(resourceProvider: provider);
    logger = new PerformanceLog(logBuffer);
    scheduler = new AnalysisDriverScheduler(logger);
    scheduler.start();
  }

  test_searchAllSubtypes() async {
    var p = _p('/test.dart');

    provider.newFile(
        p,
        '''
class T {}
class A extends T {}
class B extends A {}
class C implements B {}
''');

    var driver = _newDriver();
    driver.addFile(p);

    var resultA = await driver.getResult(p);
    ClassElement element = resultA.unit.element.types[0];

    var searchEngine = new SearchEngineImpl2([driver]);
    Set<ClassElement> subtypes = await searchEngine.searchAllSubtypes(element);
    expect(subtypes, hasLength(3));
    expect(subtypes, contains(predicate((ClassElement e) => e.name == 'A')));
    expect(subtypes, contains(predicate((ClassElement e) => e.name == 'B')));
    expect(subtypes, contains(predicate((ClassElement e) => e.name == 'C')));
  }

  test_searchAllSubtypes_acrossDrivers() async {
    var a = _p('/test/a.dart');
    var b = _p('/test/b.dart');

    provider.newFile(
        a,
        '''
class T {}
class A extends T {}
''');
    provider.newFile(
        b,
        '''
import 'a.dart';
class B extends A {}
class C extends B {}
''');

    var driver1 = _newDriver();
    var driver2 = _newDriver();

    driver1.addFile(a);
    driver2.addFile(b);

    var resultA = await driver1.getResult(a);
    ClassElement element = resultA.unit.element.types[0];

    var searchEngine = new SearchEngineImpl2([driver1, driver2]);
    Set<ClassElement> subtypes = await searchEngine.searchAllSubtypes(element);
    expect(subtypes, hasLength(3));
    expect(subtypes, contains(predicate((ClassElement e) => e.name == 'A')));
    expect(subtypes, contains(predicate((ClassElement e) => e.name == 'B')));
    expect(subtypes, contains(predicate((ClassElement e) => e.name == 'C')));
  }

  test_searchMemberDeclarations() async {
    var a = _p('/test/a.dart');
    var b = _p('/test/b.dart');

    var codeA = '''
class A {
  int test; // 1
  int testTwo;
}
''';
    var codeB = '''
class B {
  void test() {} // 2
  void testTwo() {}
}
int test;
''';

    provider.newFile(a, codeA);
    provider.newFile(b, codeB);

    var driver1 = _newDriver();
    var driver2 = _newDriver();

    driver1.addFile(a);
    driver2.addFile(b);

    while (scheduler.isAnalyzing) {
      await new Future.delayed(new Duration(milliseconds: 1));
    }

    var searchEngine = new SearchEngineImpl2([driver1, driver2]);
    List<SearchMatch> matches =
        await searchEngine.searchMemberDeclarations('test');
    expect(matches, hasLength(2));

    void assertHasElement(String name, int nameOffset) {
      expect(
          matches,
          contains(predicate((SearchMatch m) =>
              m.kind == MatchKind.DECLARATION &&
              m.element.name == name &&
              m.element.nameOffset == nameOffset)));
    }

    assertHasElement('test', codeA.indexOf('test; // 1'));
    assertHasElement('test', codeB.indexOf('test() {} // 2'));
  }

  test_searchMemberReferences() async {
    var a = _p('/test/a.dart');
    var b = _p('/test/b.dart');

    provider.newFile(
        a,
        '''
class A {
  int test;
}
foo(p) {
  p.test;
}
''');
    provider.newFile(
        b,
        '''
import 'a.dart';
bar(p) {
  p.test = 1;
}
''');

    var driver1 = _newDriver();
    var driver2 = _newDriver();

    driver1.addFile(a);
    driver2.addFile(b);

    var searchEngine = new SearchEngineImpl2([driver1, driver2]);
    List<SearchMatch> matches =
        await searchEngine.searchMemberReferences('test');
    expect(matches, hasLength(2));
    expect(
        matches,
        contains(predicate((SearchMatch m) =>
            m.element.name == 'foo' || m.kind == MatchKind.READ)));
    expect(
        matches,
        contains(predicate((SearchMatch m) =>
            m.element.name == 'bar' || m.kind == MatchKind.WRITE)));
  }

  test_searchReferences() async {
    var a = _p('/test/a.dart');
    var b = _p('/test/b.dart');

    provider.newFile(
        a,
        '''
class T {}
T a;
''');
    provider.newFile(
        b,
        '''
import 'a.dart';
T b;
''');

    var driver1 = _newDriver();
    var driver2 = _newDriver();

    driver1.addFile(a);
    driver2.addFile(b);

    var resultA = await driver1.getResult(a);
    ClassElement element = resultA.unit.element.types[0];

    var searchEngine = new SearchEngineImpl2([driver1, driver2]);
    List<SearchMatch> matches = await searchEngine.searchReferences(element);
    expect(matches, hasLength(2));
    expect(
        matches, contains(predicate((SearchMatch m) => m.element.name == 'a')));
    expect(
        matches, contains(predicate((SearchMatch m) => m.element.name == 'b')));
  }

  test_searchTopLevelDeclarations() async {
    var a = _p('/test/a.dart');
    var b = _p('/test/b.dart');

    provider.newFile(
        a,
        '''
class A {}
int a;
''');
    provider.newFile(
        b,
        '''
class B {}
get b => 42;
''');

    var driver1 = _newDriver();
    var driver2 = _newDriver();

    driver1.addFile(a);
    driver2.addFile(b);

    while (scheduler.isAnalyzing) {
      await new Future.delayed(new Duration(milliseconds: 1));
    }

    var searchEngine = new SearchEngineImpl2([driver1, driver2]);
    List<SearchMatch> matches =
        await searchEngine.searchTopLevelDeclarations('.*');
    expect(
        matches.where((match) => !match.libraryElement.isInSdk), hasLength(4));

    void assertHasElement(String name) {
      expect(
          matches,
          contains(predicate((SearchMatch m) =>
              m.kind == MatchKind.DECLARATION && m.element.name == name)));
    }

    assertHasElement('A');
    assertHasElement('a');
    assertHasElement('B');
    assertHasElement('b');
  }

  AnalysisDriver _newDriver() => new AnalysisDriver(
      scheduler,
      logger,
      provider,
      byteStore,
      contentOverlay,
      null,
      new SourceFactory(
          [new DartUriResolver(sdk), new ResourceUriResolver(provider)],
          null,
          provider),
      new AnalysisOptionsImpl()..strongMode = true);

  String _p(String path) => provider.convertPath(path);
}
