// 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/dart/analysis/results.dart';
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/error/error.dart';
import 'package:analyzer/source/line_info.dart';
import 'package:analyzer/src/dart/ast/utilities.dart';
import 'package:analyzer/src/generated/resolver.dart';

abstract class AnalysisResultImpl implements AnalysisResult {
  @override
  final AnalysisSession session;

  @override
  final String path;

  @override
  final Uri uri;

  AnalysisResultImpl(this.session, this.path, this.uri);
}

class ElementDeclarationResultImpl implements ElementDeclarationResult {
  @override
  final Element element;

  @override
  final AstNode node;

  @override
  final ParsedUnitResult parsedUnit;

  @override
  final ResolvedUnitResult resolvedUnit;

  ElementDeclarationResultImpl(
      this.element, this.node, this.parsedUnit, this.resolvedUnit);
}

class ErrorsResultImpl extends FileResultImpl implements ErrorsResult {
  @override
  final List<AnalysisError> errors;

  ErrorsResultImpl(AnalysisSession session, String path, Uri uri,
      LineInfo lineInfo, bool isPart, this.errors)
      : super(session, path, uri, lineInfo, isPart);
}

class FileResultImpl extends AnalysisResultImpl implements FileResult {
  @override
  final LineInfo lineInfo;

  @override
  final bool isPart;

  FileResultImpl(
      AnalysisSession session, String path, Uri uri, this.lineInfo, this.isPart)
      : super(session, path, uri);

  @override
  ResultState get state => ResultState.VALID;
}

class ParsedLibraryResultImpl extends AnalysisResultImpl
    implements ParsedLibraryResult {
  @override
  final ResultState state = ResultState.VALID;

  @override
  final List<ParsedUnitResult> units;

  ParsedLibraryResultImpl(
      AnalysisSession session, String path, Uri uri, this.units)
      : super(session, path, uri);

  @Deprecated('This factory exists temporary until AnalysisSession migration.')
  factory ParsedLibraryResultImpl.tmp(LibraryElement library) {
    var libraryPath = library.source.fullName;
    var analysisContext = library.context;
    var units = <ParsedUnitResult>[];
    for (var unitElement in library.units) {
      var unitSource = unitElement.source;

      if (!analysisContext.exists(unitSource)) {
        continue;
      }

      var content = analysisContext.getContents(unitSource).data;
      var lineInfo = analysisContext.getLineInfo(unitSource);
      var unit = analysisContext.parseCompilationUnit(unitSource);
      units.add(ParsedUnitResultImpl(null, unitSource.fullName, unitSource.uri,
          content, lineInfo, unitSource != library.source, unit, const []));
    }
    return ParsedLibraryResultImpl(
        null, libraryPath, library.source.uri, units);
  }

  @override
  ElementDeclarationResult getElementDeclaration(Element element) {
    var elementPath = element.source.fullName;
    var unitResult = units.firstWhere(
      (r) => r.path == elementPath,
      orElse: () {
        throw ArgumentError('Element (${element.runtimeType}) $element is not '
            'defined in this library.');
      },
    );

    if (element.isSynthetic || element.nameOffset == -1) {
      return null;
    }

    var locator = NodeLocator2(element.nameOffset);
    var node = locator.searchWithin(unitResult.unit)?.parent;
    return ElementDeclarationResultImpl(element, node, unitResult, null);
  }
}

class ParsedUnitResultImpl extends FileResultImpl implements ParsedUnitResult {
  @override
  final String content;

  @override
  final CompilationUnit unit;

  @override
  final List<AnalysisError> errors;

  ParsedUnitResultImpl(AnalysisSession session, String path, Uri uri,
      this.content, LineInfo lineInfo, bool isPart, this.unit, this.errors)
      : super(session, path, uri, lineInfo, isPart);

  @override
  ResultState get state => ResultState.VALID;
}

class ResolvedLibraryResultImpl extends AnalysisResultImpl
    implements ResolvedLibraryResult {
  @override
  final LibraryElement element;

  @override
  final ResultState state = ResultState.VALID;

  @override
  final TypeProvider typeProvider;

  @override
  final List<ResolvedUnitResult> units;

  ResolvedLibraryResultImpl(AnalysisSession session, String path, Uri uri,
      this.element, this.typeProvider, this.units)
      : super(session, path, uri);

  @override
  ElementDeclarationResult getElementDeclaration(Element element) {
    var elementPath = element.source.fullName;
    var unitResult = units.firstWhere(
      (r) => r.path == elementPath,
      orElse: () {
        throw ArgumentError('Element (${element.runtimeType}) $element is not '
            'defined in this library.');
      },
    );

    if (element.isSynthetic || element.nameOffset == -1) {
      return null;
    }

    var locator = NodeLocator2(element.nameOffset);
    var node = locator.searchWithin(unitResult.unit)?.parent;
    return ElementDeclarationResultImpl(element, node, null, unitResult);
  }

  @Deprecated('This method exists temporary until AnalysisSession migration.')
  static Future<ResolvedLibraryResult> tmp(LibraryElement library) async {
    var libraryPath = library.source.fullName;
    var units = <ResolvedUnitResult>[];
    var analysisContext = library.context;
    for (var unitElement in library.units) {
      var unitSource = unitElement.source;

      if (!analysisContext.exists(unitSource)) {
        continue;
      }

      var path = unitSource.fullName;
      var content = analysisContext.getContents(unitSource).data;
      var lineInfo = analysisContext.getLineInfo(unitSource);
      var unit = analysisContext.resolveCompilationUnit(unitSource, library);
      units.add(ResolvedUnitResultImpl(null, path, unitSource.uri, true,
          content, lineInfo, unitSource != library.source, unit, const []));
    }
    return ResolvedLibraryResultImpl(null, libraryPath, library.source.uri,
        library, library.context.typeProvider, units);
  }
}

class ResolvedUnitResultImpl extends FileResultImpl
    implements ResolvedUnitResult {
  /// Return `true` if the file exists.
  final bool exists;

  @override
  final String content;

  @override
  final CompilationUnit unit;

  @override
  final List<AnalysisError> errors;

  ResolvedUnitResultImpl(
      AnalysisSession session,
      String path,
      Uri uri,
      this.exists,
      this.content,
      LineInfo lineInfo,
      bool isPart,
      this.unit,
      this.errors)
      : super(session, path, uri, lineInfo, isPart);

  @override
  LibraryElement get libraryElement => unit.declaredElement.library;

  @override
  ResultState get state => exists ? ResultState.VALID : ResultState.NOT_A_FILE;

  @override
  TypeProvider get typeProvider => unit.declaredElement.context.typeProvider;

  @override
  TypeSystem get typeSystem => unit.declaredElement.context.typeSystem;
}

class UnitElementResultImpl extends AnalysisResultImpl
    implements UnitElementResult {
  @override
  final String signature;

  @override
  final CompilationUnitElement element;

  UnitElementResultImpl(AnalysisSession session, String path, Uri uri,
      this.signature, this.element)
      : super(session, path, uri);

  @override
  ResultState get state => ResultState.VALID;
}
