// 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.

/// Data structure storing an association between URI and file contents.
///
/// Each URI is also associated with a unique arbitrary path ending in ".dart".
/// This allows interfacing with analyzer code that expects to manipulate paths
/// rather than URIs.
class FileRepository {
  /// Regular expression matching the arbitrary file paths generated by
  /// [_pathForIndex].
  static final _pathRegexp = new RegExp(r'^/[0-9]+\.dart$');

  /// The URIs currently stored in the repository.
  final _uris = <Uri>[];

  /// Map from a URI to its index in [_uris].
  final _uriToIndexMap = <Uri, int>{};

  /// The file contents associated with the URIs in [_uris].
  final _contents = <String>[];

  /// Clear any contents stored in the file repository.  The association between
  /// URI and arbitrary path is preserved.
  ///
  /// Subsequent calls to [contentsForPath] will have undefined results until
  /// new contents are stored using [store].
  void clearContents() {
    for (var i = 0; i < _contents.length; i++) {
      _contents[i] = null;
    }
  }

  /// Return the contents of the file whose arbitary path is [path].
  ///
  /// The path must have been returned by a previous call to [store] or
  /// [pathForUri].
  String contentsForPath(String path) {
    var contents = _contents[_indexForPath(path)];
    assert(contents != null);
    return contents;
  }

  /// For testing purposes, return the contents of all files stored in the file
  /// repository, as a map from path to contents string.
  Map<String, String> getContentsForTesting() {
    var result = <String, String>{};
    for (var i = 0; i < _contents.length; i++) {
      if (_contents[i] != null) result[_pathForIndex(i)] = _contents[i];
    }
    return result;
  }

  /// Return the arbitrary path associated with [uri].
  ///
  /// If [allocate] is `false` (the default), the uri must have previously been
  /// allocated a corresponding path, e.g. via a call to [store].  If [allocate]
  /// is `true`, then a new path will be allocated if necessary.
  String pathForUri(Uri uri, {bool allocate: false}) {
    return _pathForIndex(_indexForUri(uri, allocate));
  }

  /// Associate the given [uri] with file [contents].
  ///
  /// The arbitrary path associated with the file is returned.
  String store(Uri uri, String contents) {
    int index = _indexForUri(uri, true);
    _contents[index] = contents;
    return _pathForIndex(index);
  }

  /// Return the URI for the file whose arbitrary path is [path].
  ///
  /// The path must have been returned by a previous call to [store] or
  /// [pathForUri].
  Uri uriForPath(String path) => _uris[_indexForPath(path)];

  /// Return the index into [_uris] and [_contents] matching the arbitrary path
  /// [path].
  int _indexForPath(String path) {
    assert(_pathRegexp.hasMatch(path));
    return int.parse(path.substring(1, path.length - 5));
  }

  int _indexForUri(Uri uri, bool allocate) {
    int index = _uriToIndexMap[uri];
    assert(allocate || index != null);
    if (index == null) {
      index = _uris.length;
      _uris.add(uri);
      _uriToIndexMap[uri] = index;
      _contents.add(null);
    }
    return index;
  }

  /// Return the arbitrary path associated with the given index.
  String _pathForIndex(int index) => '/$index.dart';
}
