/* -*- Mode: Vala; indent-tabs-mode: nil; tab-width: 2 -*-
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 * SPDX-FileCopyrightText: Michael Terry
 */

using GLib;

public abstract class DejaDup.BackendFile : Backend
{
  public static string test_space_free = null; // just used for testing

  public bool unmount_when_done {get; set; default = true;}

  bool will_unmount = false;

  public override async void cleanup() {
    // unmount if we originally mounted the backup location
    if (will_unmount && unmount_when_done) {
      yield unmount();
      will_unmount = false;
    }
  }

  public override string[] get_dependencies()
  {
    return Config.GVFS_PACKAGES.split(",");
  }

  // Get mountable root
  protected abstract File? get_root_from_settings();

  // Get full URI to backup folder
  internal abstract File? get_file_from_settings();

  public override string get_location_pretty()
  {
    if (DejaDup.in_demo_mode())
      return "hostname on Google Drive";

    var file = get_file_from_settings();
    if (file == null)
      return "";
    return get_file_desc(file);
  }

  protected override string get_unique_location()
  {
    var file = get_file_from_settings();
    if (file == null) {
      warning("File URI is null in get_unique_location.");
      return "file:";
    }
    return "file:" + file.get_uri();
  }

  public override bool is_native() {
    return true;
  }

  // will be mounted by this time
  public override void add_excludes(ref List<File> excludes)
  {
    var file = get_file_from_settings();
    if (file != null && file.is_native())
      excludes.append(file);
  }

  async bool query_exists_async(File file)
  {
    try {
      yield file.query_info_async(FileAttribute.STANDARD_TYPE,
                                  FileQueryInfoFlags.NONE,
                                  Priority.LOW, null);
      return true;
    }
    catch (Error e) {
      return false;
    }
  }

  public override async List<string> peek_at_files()
  {
    var filenames = new List<string>();
    var gfile = get_file_from_settings();
    try {
      var enumerator = yield gfile.enumerate_children_async(
         FileAttribute.STANDARD_NAME + "," + FileAttribute.STANDARD_IS_HIDDEN,
         FileQueryInfoFlags.NONE,
         Priority.LOW,
         null
      );
      while (filenames.length() < 20) {
        var infos = yield enumerator.next_files_async(20, Priority.LOW, null);
        if (infos.length() == 0)
          break; // no more files
        foreach (var info in infos) {
          if (!info.has_attribute(FileAttribute.STANDARD_IS_HIDDEN) || !info.get_is_hidden())
            filenames.append(info.get_name());
        }
      }
      return filenames;
    }
    catch (Error e) {
      // ignore, just return no files
      return filenames;
    }
  }

  public override async void prepare() throws Error
  {
    will_unmount = (yield mount()) || will_unmount;

    var gfile = get_file_from_settings();

    // Ensure directory exists (we check first rather than just doing it,
    // because this makes some backends -- like google-drive: -- work better,
    // as they allow multiple files with the same name. Querying it
    // anchors the path to the backend object and we don't create a second
    // copy this way.
    if (gfile != null && !(yield query_exists_async(gfile))) {
      try {
        gfile.make_directory_with_parents (null);
      }
      catch (IOError.EXISTS err2) {
        // ignore
      }
    }
  }

  // Returns true if it needed to be mounted, false if already mounted
  public virtual async bool mount() throws Error {return false;}

  protected virtual async void unmount()
  {
    var root = get_root_from_settings();
    try {
      var mount = yield root.find_enclosing_mount_async();
      if (mount != null && mount.can_unmount())
        yield mount.unmount_with_operation(MountUnmountFlags.NONE, null);
    }
    catch (Error e) {
      // ignore
    }
  }

  public static string escape_uri_chars(string path)
  {
    return path
      .replace("%", "%25")
      .replace("?", "%3F")
      .replace("#", "%23");
  }

  public string? get_path_as_url()
  {
    var file = get_file_from_settings();
    if (file == null || file.get_path() == null)
      return null;

    var escaped = escape_uri_chars(file.get_path());
    return "file://" + escaped;
  }

  // Converts any mentions of the local path to the URI
  public string? replace_path_with_uri(string? message)
  {
    if (message == null)
      return null;

    var file = get_file_from_settings();
    if (file == null)
      return message;

    string path = file.get_path();
    string uri = file.get_uri();
    if (path == null || uri == null)
      return message;

    // Strip trailing slashes (one might, one might not have it)
    if (path.data[path.length - 1] == '/')
      path.data[path.length - 1] = 0;
    if (uri.data[uri.length - 1] == '/')
      uri.data[uri.length - 1] = 0;

    return message.replace(path, uri);
  }

  public override async void get_space(out uint64 free, out uint64 total)
  {
    yield base.get_space(out free, out total);

    var file = get_file_from_settings();
    if (file == null)
      return;

    FileInfo info;
    try {
      info = yield file.query_filesystem_info_async(
        FileAttribute.FILESYSTEM_FREE + "," + FileAttribute.FILESYSTEM_SIZE,
        Priority.LOW, null
      );
    } catch (Error e) {
      warning("Could not query GVfs space: %s\n", e.message);
      return;
    }

    if (in_testing_mode() && test_space_free != null)
    {
      var free_list = test_space_free.split(";");
      free = uint64.parse(free_list[0]);
      if (free_list[1] != null) {
        var space_free = string.joinv(";", free_list[1:free_list.length]);
        test_space_free = space_free;
      }
    }
    else if (info.has_attribute(FileAttribute.FILESYSTEM_FREE)) {
      free = info.get_attribute_uint64(FileAttribute.FILESYSTEM_FREE);
      if (free == INFINITE_SPACE)
        free -= 1; // avoid accidentally reporting infinite
    }

    if (info.has_attribute(FileAttribute.FILESYSTEM_SIZE)) {
      total = info.get_attribute_uint64(FileAttribute.FILESYSTEM_SIZE);
      if (total == INFINITE_SPACE)
        total -= 1; // avoid accidentally reporting infinite
    }

    // Sanity check total here, plus this can actually happen if an overflow
    // occurs (GNOME bug 786177).
    if (free != INFINITE_SPACE && free > total)
      total = free;
  }
}
