/* Copyright 2016 Software Freedom Conservancy Inc.
 *
 * This software is licensed under the GNU LGPL (version 2.1 or later).
 * See the COPYING file in this distribution.
 */

class AppDirs {
    private const string DEFAULT_DATA_DIR = "shotwell";
    
    private static File exec_dir;
    private static File data_dir = null;
    private static File tmp_dir = null;
    private static File libexec_dir = null;
    
    // Because this is called prior to Debug.init(), this function cannot do any logging calls
    public static void init(string arg0) {
        File exec_file = File.new_for_path(Posix.realpath(Environment.find_program_in_path(arg0)));
        exec_dir = exec_file.get_parent();
    }
    
    // Because this *may* be called prior to Debug.init(), this function cannot do any logging
    // calls
    public static void terminate() {
    }
    
    public static File get_home_dir() {
        return File.new_for_path(Environment.get_home_dir());
    }

    public static File get_cache_dir() {
        return ((data_dir == null) ?
            File.new_for_path(Environment.get_user_cache_dir()).get_child(DEFAULT_DATA_DIR) :
            data_dir);
    }
    
    public static void try_migrate_data() {
        // Migrate the user plugin dir from .gnome2 to .local
        File user_plugin_dir = get_user_plugins_dir();
        File old_dir =
            get_home_dir().get_child(".gnome2").get_child("shotwell").get_child("plugins");

        if (old_dir.query_exists() && !user_plugin_dir.get_parent().query_exists()) {
            try {
              user_plugin_dir.get_parent().make_directory_with_parents(null);
            } catch (Error err) { }
        }

        try {
            old_dir.move(user_plugin_dir, FileCopyFlags.NONE);
        } catch (Error err) { }


        File new_dir = get_data_dir();
        old_dir = get_home_dir().get_child(".shotwell");
        if (new_dir.query_exists() || !old_dir.query_exists())
            return;

        File cache_dir = get_cache_dir();
        Posix.mode_t mask = Posix.umask(0077);
        if (!cache_dir.query_exists()) {
            try {
                cache_dir.make_directory_with_parents(null);
            } catch (Error err) {
                AppWindow.panic(_("Unable to create cache directory %s: %s").printf(cache_dir.get_path(),
                                                                                    err.message));
            }
        }
        GLib.FileUtils.rename(old_dir.get_child("thumbs").get_path(), cache_dir.get_child("thumbs").get_path());

        if (!new_dir.get_parent().query_exists()) {
            try {
              new_dir.get_parent().make_directory_with_parents(null);
            } catch (Error err) {
                AppWindow.panic(_("Unable to create data directory %s: %s").printf(new_dir.get_parent().get_path(),
                                                                                   err.message));
            }
        }
        GLib.FileUtils.rename(old_dir.get_path(), new_dir.get_path());
        GLib.FileUtils.chmod(new_dir.get_path(), 0700);

        Posix.umask(mask);
    }

    // This can only be called once, and it better be called at startup
    public static void set_data_dir(string user_data_dir) requires (!is_string_empty(user_data_dir)) {
        assert(data_dir == null);
        
        // fix up to absolute path
        string path = strip_pretty_path(user_data_dir);
        if (!Path.is_absolute(path))
            data_dir = get_home_dir().get_child(path);
        else
            data_dir = File.new_for_path(path);
        
        message("Setting private data directory to %s", data_dir.get_path());
    }
    
    public static void verify_data_dir() {
        File data_dir = get_data_dir();
        try {
            if (!data_dir.query_exists(null))
                data_dir.make_directory_with_parents(null);
        } catch (Error err) {
            AppWindow.panic(_("Unable to create data directory %s: %s").printf(data_dir.get_path(),
                err.message));
        }
    }
    
    public static void verify_cache_dir() {
        File cache_dir = get_cache_dir();
        try {
            if (!cache_dir.query_exists(null))
                cache_dir.make_directory_with_parents(null);
        } catch (Error err) {
            AppWindow.panic(_("Unable to create cache directory %s: %s").printf(cache_dir.get_path(),
                err.message));
        }
    }
    
    /**
     * @brief Returns the build directory if not installed yet, or a path
     * to where any helper applets we need will live if installed.
     */
    public static File get_libexec_dir() {
        if (libexec_dir == null) {
            if (get_install_dir() == null) {
                // not installed yet - use wherever we were run from
                libexec_dir = get_exec_dir();
            } else {
                libexec_dir = File.new_for_path(Resources.LIBEXECDIR);
            }
        }

        return libexec_dir;
    }

    // Return the directory in which Shotwell is installed, or null if uninstalled.
    public static File? get_install_dir() {
        return get_sys_install_dir(exec_dir);
    }
    
    public static File get_data_dir() {
        return (data_dir == null) ? File.new_for_path(Environment.get_user_data_dir()).get_child(DEFAULT_DATA_DIR) : data_dir;
    }
    
    // The "import directory" is the same as the library directory, and are often used
    // interchangeably throughout the code.
    public static File get_import_dir() {
        string path = Config.Facade.get_instance().get_import_dir();
        if (!is_string_empty(path)) {
            // tilde -> home directory
            path = strip_pretty_path(path);
            
            // if non-empty and relative, make it relative to the user's home directory
            if (!Path.is_absolute(path)) 
                return get_home_dir().get_child(path);
            
            // non-empty and absolute, it's golden
            return File.new_for_path(path);
        }
        
        // Empty path, use XDG Pictures directory
        path = Environment.get_user_special_dir(UserDirectory.PICTURES);
        if (!is_string_empty(path))
            return File.new_for_path(path);
        
        // If XDG yarfed, use ~/Pictures
        return get_home_dir().get_child(_("Pictures"));
    }
    
    // Library folder + photo folder, based on user's preferred directory pattern.
    public static File get_baked_import_dir(DateTime tm) {
        string? pattern = Config.Facade.get_instance().get_directory_pattern();
        if (is_string_empty(pattern))
            pattern = Config.Facade.get_instance().get_directory_pattern_custom();
        if (is_string_empty(pattern))
            pattern = "%Y" + Path.DIR_SEPARATOR_S + "%m" + Path.DIR_SEPARATOR_S + "%d"; // default
            
        return File.new_for_path(get_import_dir().get_path() + Path.DIR_SEPARATOR_S + tm.to_local().format(pattern));
    }
    
    // Returns true if the File is in or is equal to the library/import directory.
    public static bool is_in_import_dir(File file) {
        File import_dir = get_import_dir();
        
        return file.has_prefix(import_dir) || file.equal(import_dir);
    }

    public static void set_import_dir(File file) {
        Config.Facade.get_instance().set_import_dir(file.get_path());
    }
    
    public static File get_exec_dir() {
        return exec_dir;
    }

    public enum Runtime {
        NATIVE,
        FLATPAK,
        SNAP,
        UNKNOWN
    }

    private static Runtime _runtime = Runtime.UNKNOWN;

    public static Runtime get_runtime() {
        if (_runtime == Runtime.UNKNOWN) {
            var snap = Environment.get_variable("SNAP_NAME");
            if (snap != null) {
                _runtime = Runtime.SNAP;
            }
            else {
                var flatpak_canary = File.new_for_path("/.flatpak-info");
                if (flatpak_canary.query_exists()) {
                    _runtime = Runtime.FLATPAK;
                } else {
                    _runtime = Runtime.NATIVE;
                }
            }
        }

        return _runtime;
    }
    
    public static File get_temp_dir() {
        if (tmp_dir == null) {
            var basedir = Environment.get_tmp_dir();
            if (get_runtime() == Runtime.FLATPAK && basedir == "/tmp") {
                basedir = Environment.get_user_cache_dir() + "/tmp";
            }

            tmp_dir = File.new_for_path(DirUtils.mkdtemp (basedir + "/shotwell-XXXXXX"));
            
            try {
                tmp_dir.make_directory_with_parents(null);
            } catch (Error err) {
                if (!(err is GLib.IOError.EXISTS)) {
                    AppWindow.panic(_("Unable to create temporary directory %s: %s").printf(
                        tmp_dir.get_path(), err.message));
                }
            }
        }
        
        return tmp_dir;
    }

    public static File get_data_subdir(string name, string? subname = null) {
        File subdir = get_data_dir().get_child(name);
        if (subname != null)
            subdir = subdir.get_child(subname);

        try {
            if (!subdir.query_exists(null))
                subdir.make_directory_with_parents(null);
        } catch (Error err) {
            AppWindow.panic(_("Unable to create data subdirectory %s: %s").printf(subdir.get_path(),
                err.message));
        }
        
        return subdir;
    }

    public static void ensure_writable(File dir) {
        if (dir.query_exists(null)) {
            try {
                FileInfo info = dir.query_info(FileAttribute.UNIX_MODE, FileQueryInfoFlags.NONE);
                uint32 mode = info.get_attribute_uint32(FileAttribute.UNIX_MODE) | 0700;
                if (!dir.set_attribute_uint32(FileAttribute.UNIX_MODE, mode, FileQueryInfoFlags.NONE)) {
                    AppWindow.panic(_("Could not make directory %s writable").printf(dir.get_path()));
                }
            } catch (Error err) {
                AppWindow.panic(_("Could not make directory %s writable: %s").printf(dir.get_path(), err.message));
            }
        }
    }

    public static File get_cache_subdir(string name, string? subname = null) {
        File subdir = get_cache_dir().get_child(name);
        ensure_writable(subdir);
        if (subname != null)
            subdir = subdir.get_child(subname);

        try {
            if (!subdir.query_exists(null))
                subdir.make_directory_with_parents(null);
        } catch (Error err) {
            AppWindow.panic(_("Unable to create data subdirectory %s: %s").printf(subdir.get_path(),
                err.message));
        }
        ensure_writable(subdir);
        return subdir;
    }
    
    public static File get_resources_dir() {
        File? install_dir = get_install_dir();
        
        return (install_dir != null) ? install_dir.get_child("share").get_child("shotwell")
            : get_lib_dir();
    }
    
    public static File get_lib_dir() {
        File? install_dir = get_install_dir();

        // Running from source tree
        if (install_dir == null) {
            // Meson build
            if (get_exec_dir().get_path().has_suffix("src")) {
                return get_exec_dir().get_parent();
            }

            return get_exec_dir();
        }
        
        return install_dir.get_child(Resources.LIB).get_child("shotwell");
    }
    
    public static File get_system_plugins_dir() {
        return get_lib_dir().get_child("plugins");
    }
    
    public static File get_user_plugins_dir() {
        return get_data_dir().get_child("plugins");
    }
    
    public static File? get_log_file() {
        if (Environment.get_variable("SHOTWELL_LOG_FILE") != null) {
            if (Environment.get_variable("SHOTWELL_LOG_FILE") == ":console:") {
                return null;
            } else {
                return File.new_for_path(Environment.get_variable("SHOTWELL_LOG_FILE"));
            }
        } else {
            return File.new_for_path(Environment.get_user_cache_dir()).
                get_child("shotwell").get_child("shotwell.log");
        }
    }
    
    public static File get_thumbnailer_bin() {
        const string filename = "shotwell-video-thumbnailer";
        File f = AppDirs.get_libexec_dir().get_child("thumbnailer").get_child (filename);
        if (!f.query_exists()) {
            // If we're running installed.
            f = AppDirs.get_libexec_dir () .get_child ("shotwell").get_child (filename);
        }

        if (!f.query_exists()) {
            f = AppDirs.get_libexec_dir().get_parent().get_child("thumbnailer").get_child(filename);
        }

        return f;
    }

    public static File get_metadata_helper() {
        const string filename = "shotwell-video-metadata-handler";
        File f = AppDirs.get_libexec_dir().get_child("video-support").get_child (filename);
        if (!f.query_exists()) {
            // If we're running installed.
            f = AppDirs.get_libexec_dir () .get_child ("shotwell").get_child (filename);
        }
        return f;
    }

    public static File get_settings_migrator_bin() {
        const string filename = "shotwell-settings-migrator";
        File f = AppDirs.get_libexec_dir().get_child("settings-migrator").get_child (filename);
        if (!f.query_exists()) {
            // If we're running installed.
            f = AppDirs.get_libexec_dir () .get_child ("shotwell").get_child (filename);
        }

        if (!f.query_exists()) {
            f = AppDirs.get_libexec_dir().get_parent().get_child("settings-migrator").get_child(filename);
        }

        return f;
    }

    public static File get_haarcascade_file() {
        const string filename = "facedetect-haarcascade.xml";
        var f = AppDirs.get_resources_dir().get_parent().get_child("subprojects").get_child("shotwell-facedetect").get_child (filename);
        if (f.query_exists()) {//testing meson builddir
            return f;
        }
        return get_resources_dir().get_child("facedetect-haarcascade.xml");
    }


#if ENABLE_FACE_DETECTION
    public static File get_facedetect_bin() {
        const string filename = "shotwell-facedetect";
        File f = AppDirs.get_libexec_dir().get_parent().get_child("subprojects").get_child(filename).get_child (filename);
        if (!f.query_exists()) {
            f = AppDirs.get_libexec_dir().get_child("shotwell").get_child(filename);
        }
        return f;
    }

    public static File get_openface_dnn_dir() {
        return File.new_for_path(Environment.get_user_data_dir()).get_child(DEFAULT_DATA_DIR).get_child("facedetect");
    }

    public static File get_openface_dnn_system_dir() {
        var f = File.new_for_path("/app/extra");
        if (f.query_exists())
            return f;

        f = AppDirs.get_resources_dir().get_parent().get_child("subprojects").get_child("shotwell-facedetect");
        if (f.query_exists()) {//testing meson builddir
            return f;
        }
    
        return AppDirs.get_resources_dir().get_child("facedetect");
    }

#endif
}
