// 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:collection';

import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/source/source.dart';
import 'package:analyzer/src/generated/engine.dart';

/// A source that represents a file.
class FileSource extends Source {
  /// Map from encoded URI/filepath pair to a unique integer identifier.  This
  /// identifier is used for equality tests and hash codes.
  ///
  /// The URI and filepath are joined into a pair by separating them with an '@'
  /// character.
  static final Map<String, int> _idTable = HashMap<String, int>();

  /// The URI from which this source was originally derived.
  @override
  final Uri uri;

  /// The unique ID associated with this source.
  final int id;

  /// The file represented by this source.
  final File file;

  /// The cached absolute path of this source.
  String? _absolutePath;

  /// Initialize a newly created source object to represent the given [file]. If
  /// a [uri] is given, then it will be used as the URI from which the source
  /// was derived, otherwise a `file:` URI will be created based on the [file].
  FileSource(this.file, [Uri? uri])
      : uri = uri ?? file.toUri(),
        id = _idTable.putIfAbsent(
            '${uri ?? file.toUri()}@${file.path}', () => _idTable.length);

  @override
  TimestampedData<String> get contents {
    return contentsFromFile;
  }

  /// Get and return the contents and timestamp of the underlying file.
  ///
  /// Clients should consider using the method
  /// `AnalysisSession.getFile('...').contents` because contexts can have local
  /// overrides of the content of a source that the source is not aware of.
  ///
  /// Throws an exception if the contents of this source could not be accessed.
  /// See [contents].
  TimestampedData<String> get contentsFromFile {
    return TimestampedData<String>(
      file.modificationStamp,
      file.readAsStringSync(),
    );
  }

  @override
  String get fullName => _absolutePath ??= file.path;

  @override
  int get hashCode => uri.hashCode;

  @override
  String get shortName => file.shortName;

  @override
  bool operator ==(Object other) {
    if (other is FileSource) {
      return id == other.id;
    } else if (other is Source) {
      return uri == other.uri;
    }
    return false;
  }

  @override
  bool exists() => file.exists;

  @override
  String toString() {
    return file.path;
  }
}
