/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */

#include "common.h"
#include "utils.h"

#include "log.h"

#include "seafile-session.h"
#include "share-mgr.h"

#include "seaf-db.h"
#include "log.h"
#include "seafile-error.h"

SeafShareManager *
seaf_share_manager_new (SeafileSession *seaf)
{
    SeafShareManager *mgr = g_new0 (SeafShareManager, 1);

    mgr->seaf = seaf;

    return mgr;
}

int
seaf_share_manager_start (SeafShareManager *mgr)
{
    if (!mgr->seaf->create_tables && seaf_db_type (mgr->seaf->db) != SEAF_DB_TYPE_PGSQL)
        return 0;

    SeafDB *db = mgr->seaf->db;
    const char *sql;

    int db_type = seaf_db_type (db);
    if (db_type == SEAF_DB_TYPE_MYSQL) {
        sql = "CREATE TABLE IF NOT EXISTS SharedRepo "
            "(id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT,"
            "repo_id CHAR(37) , from_email VARCHAR(255), to_email VARCHAR(255), "
            "permission CHAR(15), INDEX (repo_id), "
            "INDEX(from_email), INDEX(to_email)) ENGINE=INNODB";

        if (seaf_db_query (db, sql) < 0)
            return -1;
    } else if (db_type == SEAF_DB_TYPE_SQLITE) {
        sql = "CREATE TABLE IF NOT EXISTS SharedRepo "
            "(repo_id CHAR(37) , from_email VARCHAR(255), to_email VARCHAR(255), "
            "permission CHAR(15))";
        if (seaf_db_query (db, sql) < 0)
            return -1;
        sql = "CREATE INDEX IF NOT EXISTS RepoIdIndex on SharedRepo (repo_id)";
        if (seaf_db_query (db, sql) < 0)
            return -1;
        sql = "CREATE INDEX IF NOT EXISTS FromEmailIndex on SharedRepo (from_email)";
        if (seaf_db_query (db, sql) < 0)
            return -1;
        sql = "CREATE INDEX IF NOT EXISTS ToEmailIndex on SharedRepo (to_email)";
        if (seaf_db_query (db, sql) < 0)
            return -1;
    }
    /* else if (db_type == SEAF_DB_TYPE_PGSQL) { */
    /*     sql = "CREATE TABLE IF NOT EXISTS SharedRepo " */
    /*         "(repo_id CHAR(36) , from_email VARCHAR(255), to_email VARCHAR(255), " */
    /*         "permission VARCHAR(15))"; */
    /*     if (seaf_db_query (db, sql) < 0) */
    /*         return -1; */

    /*     if (!pgsql_index_exists (db, "sharedrepo_repoid_idx")) { */
    /*         sql = "CREATE INDEX sharedrepo_repoid_idx ON SharedRepo (repo_id)"; */
    /*         if (seaf_db_query (db, sql) < 0) */
    /*             return -1; */
    /*     } */
    /*     if (!pgsql_index_exists (db, "sharedrepo_from_email_idx")) { */
    /*         sql = "CREATE INDEX sharedrepo_from_email_idx ON SharedRepo (from_email)"; */
    /*         if (seaf_db_query (db, sql) < 0) */
    /*             return -1; */
    /*     } */
    /*     if (!pgsql_index_exists (db, "sharedrepo_to_email_idx")) { */
    /*         sql = "CREATE INDEX sharedrepo_to_email_idx ON SharedRepo (to_email)"; */
    /*         if (seaf_db_query (db, sql) < 0) */
    /*             return -1; */
    /*     } */
    /* } */
    
    return 0;
}

int
seaf_share_manager_add_share (SeafShareManager *mgr, const char *repo_id,
                              const char *from_email, const char *to_email,
                              const char *permission)
{
    gboolean db_err = FALSE;
    int ret = 0;

    char *from_email_l = g_ascii_strdown (from_email, -1);
    char *to_email_l = g_ascii_strdown (to_email, -1);

    if (seaf_db_statement_exists (mgr->seaf->db,
                                  "SELECT repo_id from SharedRepo "
                                  "WHERE repo_id=? AND "
                                  "from_email=? AND to_email=?",
                                  &db_err, 3, "string", repo_id,
                                  "string", from_email_l, "string", to_email_l))
        goto out;

    if (seaf_db_statement_query (mgr->seaf->db,
                                 "INSERT INTO SharedRepo (repo_id, from_email, "
                                 "to_email, permission) VALUES (?, ?, ?, ?)",
                                 4, "string", repo_id, "string", from_email_l,
                                 "string", to_email_l, "string", permission) < 0) {
        ret = -1;
        goto out;
    }

out:
    g_free (from_email_l);
    g_free (to_email_l);
    return ret;
}

int
seaf_share_manager_set_subdir_perm_by_path (SeafShareManager *mgr, const char *repo_id,
                                           const char *from_email, const char *to_email,
                                           const char *permission, const char *path)
{
    char *sql;
    int ret;

    char *from_email_l = g_ascii_strdown (from_email, -1);
    char *to_email_l = g_ascii_strdown (to_email, -1);
    sql = "UPDATE SharedRepo SET permission=? WHERE repo_id IN "
          "(SELECT repo_id FROM VirtualRepo WHERE origin_repo=? AND path=?) "
          "AND from_email=? AND to_email=?";

    ret = seaf_db_statement_query (mgr->seaf->db, sql,
                                   5, "string", permission,
                                   "string", repo_id,
                                   "string", path,
                                   "string", from_email_l,
                                   "string", to_email_l);
    g_free (from_email_l);
    g_free (to_email_l);
    return ret;
}

int
seaf_share_manager_set_permission (SeafShareManager *mgr, const char *repo_id,
                                   const char *from_email, const char *to_email,
                                   const char *permission)
{
    char *sql;
    int ret;

    char *from_email_l = g_ascii_strdown (from_email, -1);
    char *to_email_l = g_ascii_strdown (to_email, -1);
    sql = "UPDATE SharedRepo SET permission=? WHERE "
        "repo_id=? AND from_email=? AND to_email=?";

    ret = seaf_db_statement_query (mgr->seaf->db, sql,
                                   4, "string", permission, "string", repo_id,
                                   "string", from_email_l, "string", to_email_l);

    g_free (from_email_l);
    g_free (to_email_l);
    return ret;
}

static gboolean
collect_repos (SeafDBRow *row, void *data)
{
    GList **p_repos = data;
    const char *repo_id;
    const char *vrepo_id;
    const char *email;
    const char *permission;
    const char *commit_id;
    gint64 size;
    SeafileRepo *repo;

    repo_id = seaf_db_row_get_column_text (row, 0);
    vrepo_id = seaf_db_row_get_column_text (row, 1);
    email = seaf_db_row_get_column_text (row, 2);
    permission = seaf_db_row_get_column_text (row, 3);
    commit_id = seaf_db_row_get_column_text (row, 4);
    size = seaf_db_row_get_column_int64 (row, 5);
    const char *repo_name = seaf_db_row_get_column_text (row, 8);
    gint64 update_time = seaf_db_row_get_column_int64 (row, 9);
    int version = seaf_db_row_get_column_int (row, 10); 
    gboolean is_encrypted = seaf_db_row_get_column_int (row, 11) ? TRUE : FALSE;
    const char *last_modifier = seaf_db_row_get_column_text (row, 12);
    int status = seaf_db_row_get_column_int (row, 13);
    const char *type = seaf_db_row_get_column_text (row, 14);
    const char *origin_repo_name = seaf_db_row_get_column_text (row, 15);

    char *email_l = g_ascii_strdown (email, -1);

    repo = g_object_new (SEAFILE_TYPE_REPO,
                         "share_type", "personal",
                         "repo_id", repo_id,
                         "id", repo_id,
                         "head_cmmt_id", commit_id,
                         "user", email_l,
                         "permission", permission,
                         "is_virtual", (vrepo_id != NULL),
                         "size", size,
                         "status", status,
                         NULL);
    g_free (email_l);

    if (repo) {
        if (vrepo_id) {
            const char *origin_repo_id = seaf_db_row_get_column_text (row, 6);
            const char *origin_path = seaf_db_row_get_column_text (row, 7);
            g_object_set (repo, "store_id", origin_repo_id,
                          "origin_repo_id", origin_repo_id,
                          "origin_repo_name", origin_repo_name,
                          "origin_path", origin_path, NULL);
        } else {
            g_object_set (repo, "store_id", repo_id, NULL);
        }
        if (repo_name) {
            g_object_set (repo, "name", repo_name,
                          "repo_name", repo_name,
                          "last_modify", update_time,
                          "last_modified", update_time,
                          "version", version,
                          "encrypted", is_encrypted,
                          "last_modifier", last_modifier, NULL);
        }
        if (type) {
            g_object_set (repo, "repo_type", type, NULL);
        }
        *p_repos = g_list_prepend (*p_repos, repo);
    }

    return TRUE;
}

static void
seaf_fill_repo_commit_if_not_in_db (GList **repos)
{
    char *repo_name = NULL;
    char *last_modifier = NULL;
    char *repo_id = NULL;
    char *commit_id = NULL;
    SeafileRepo *repo = NULL;
    GList *p = NULL;

    for (p = *repos; p;) {
        repo = p->data;
        g_object_get (repo, "name", &repo_name, NULL);
        g_object_get (repo, "last_modifier", &last_modifier, NULL);
        if (!repo_name || !last_modifier) {
            g_object_get (repo, "repo_id", &repo_id,
                          "head_cmmt_id", &commit_id, NULL);
            SeafCommit *commit = seaf_commit_manager_get_commit_compatible (seaf->commit_mgr,
                                                                            repo_id, commit_id);
            if (!commit) {
                seaf_warning ("Commit %s:%s is missing\n", repo_id, commit_id);
                GList *next = p->next;
                g_object_unref (repo);
                *repos = g_list_delete_link (*repos, p);
                p = next;
                if (repo_name)
                    g_free (repo_name);
                if (last_modifier)
                    g_free (last_modifier);
                continue;
            } else {
                g_object_set (repo, "name", commit->repo_name,
                                    "repo_name", commit->repo_name,
                                    "last_modify", commit->ctime,
                                    "last_modified", commit->ctime,
                                    "version", commit->version,
                                    "encrypted", commit->encrypted,
                                    "last_modifier", commit->creator_name,
                                    NULL);

                /* Set to database */
                set_repo_commit_to_db (repo_id, commit->repo_name, commit->ctime, commit->version,
                                       commit->encrypted, commit->creator_name);

                seaf_commit_unref (commit);
            }
            g_free (repo_id);
            g_free (commit_id);
        }
        if (repo_name)
            g_free (repo_name);
        if (last_modifier)
            g_free (last_modifier);

        p = p->next;
    }
}

GList*
seaf_share_manager_list_share_repos (SeafShareManager *mgr, const char *email,
                                     const char *type, int start, int limit,
                                     gboolean *db_err)
{
    GList *ret = NULL, *p;
    char *sql;

    if (start == -1 && limit == -1) {
        if (g_strcmp0 (type, "from_email") == 0) {
            sql = "SELECT sh.repo_id, v.repo_id, "
                "to_email, permission, commit_id, s.size, "
                "v.origin_repo, v.path, i.name, "
                "i.update_time, i.version, i.is_encrypted, i.last_modifier, i.status, i.type, "
                "i2.name FROM "
                "SharedRepo sh LEFT JOIN VirtualRepo v ON "
                "sh.repo_id=v.repo_id "
                "LEFT JOIN RepoSize s ON sh.repo_id = s.repo_id "
                "LEFT JOIN RepoInfo i ON sh.repo_id = i.repo_id "
                "LEFT JOIN RepoInfo i2 ON v.origin_repo = i2.repo_id, Branch b "
                "WHERE from_email=? AND "
                "sh.repo_id = b.repo_id AND "
                "b.name = 'master' "
                "ORDER BY i.update_time DESC, sh.repo_id";
        } else if (g_strcmp0 (type, "to_email") == 0) {
            sql = "SELECT sh.repo_id, v.repo_id, "
                "from_email, permission, commit_id, s.size, "
                "v.origin_repo, v.path, i.name, "
                "i.update_time, i.version, i.is_encrypted, i.last_modifier, i.status, i.type, "
                "i2.name FROM "
                "SharedRepo sh LEFT JOIN VirtualRepo v ON "
                "sh.repo_id=v.repo_id "
                "LEFT JOIN RepoSize s ON sh.repo_id = s.repo_id "
                "LEFT JOIN RepoInfo i ON sh.repo_id = i.repo_id "
                "LEFT JOIN RepoInfo i2 ON v.origin_repo = i2.repo_id, Branch b "
                "WHERE to_email=? AND "
                "sh.repo_id = b.repo_id AND "
                "b.name = 'master' "
                "ORDER BY i.update_time DESC, sh.repo_id";
        } else {
            /* should never reach here */
            seaf_warning ("[share mgr] Wrong column type");
            return NULL;
        }

        if (seaf_db_statement_foreach_row (mgr->seaf->db, sql,
                                           collect_repos, &ret,
                                           1, "string", email) < 0) {
            seaf_warning ("[share mgr] DB error when get shared repo id and email "
                       "for %s.\n", email);
            for (p = ret; p; p = p->next)
                g_object_unref (p->data);
            g_list_free (ret);
            if (db_err)
                *db_err = TRUE;
            return NULL;
        }
    }
    else {
        if (g_strcmp0 (type, "from_email") == 0) {
            sql = "SELECT sh.repo_id, v.repo_id, "
                "to_email, permission, commit_id, s.size, "
                "v.origin_repo, v.path, i.name, "
                "i.update_time, i.version, i.is_encrypted, i.last_modifier, i.status, i.type, "
                "i2.name FROM "
                "SharedRepo sh LEFT JOIN VirtualRepo v ON "
                "sh.repo_id=v.repo_id "
                "LEFT JOIN RepoSize s ON sh.repo_id = s.repo_id "
                "LEFT JOIN RepoInfo i ON sh.repo_id = i.repo_id "
                "LEFT JOIN RepoInfo i2 ON v.origin_repo = i2.repo_id, Branch b "
                "WHERE from_email=? "
                "AND sh.repo_id = b.repo_id "
                "AND b.name = 'master' "
                "ORDER BY i.update_time DESC, sh.repo_id "
                "LIMIT ? OFFSET ?";
        } else if (g_strcmp0 (type, "to_email") == 0) {
            sql = "SELECT sh.repo_id, v.repo_id, "
                "from_email, permission, commit_id, s.size, "
                "v.origin_repo, v.path, i.name, "
                "i.update_time, i.version, i.is_encrypted, i.last_modifier, i.status, i.type, "
                "i2.name FROM "
                "SharedRepo sh LEFT JOIN VirtualRepo v ON "
                "sh.repo_id=v.repo_id "
                "LEFT JOIN RepoSize s ON sh.repo_id = s.repo_id "
                "LEFT JOIN RepoInfo i ON sh.repo_id = i.repo_id "
                "LEFT JOIN RepoInfo i2 ON v.origin_repo = i2.repo_id, Branch b "
                "WHERE to_email=? "
                "AND sh.repo_id = b.repo_id "
                "AND b.name = 'master' "
                "ORDER BY i.update_time DESC, sh.repo_id "
                "LIMIT ? OFFSET ?";
        } else {
            /* should never reach here */
            seaf_warning ("[share mgr] Wrong column type");
            return NULL;
        }

        if (seaf_db_statement_foreach_row (mgr->seaf->db, sql,
                                           collect_repos, &ret,
                                           3, "string", email,
                                           "int", limit, "int", start) < 0) {
            seaf_warning ("[share mgr] DB error when get shared repo id and email "
                       "for %s.\n", email);
            for (p = ret; p; p = p->next)
                g_object_unref (p->data);
            g_list_free (ret);
            if (db_err)
                *db_err = TRUE;
            return NULL;
        }
    }

    seaf_fill_repo_commit_if_not_in_db (&ret);

    return g_list_reverse (ret);
}

static gboolean
collect_shared_to (SeafDBRow *row, void *data)
{
    GList **plist = data;
    const char *to_email;

    to_email = seaf_db_row_get_column_text (row, 0);
    *plist = g_list_prepend (*plist, g_ascii_strdown(to_email, -1));

    return TRUE;
}

GList *
seaf_share_manager_list_shared_to (SeafShareManager *mgr,
                                   const char *owner,
                                   const char *repo_id)
{
    char *sql;
    GList *ret = NULL;

    sql = "SELECT to_email FROM SharedRepo WHERE "
        "from_email=? AND repo_id=?";
    if (seaf_db_statement_foreach_row (mgr->seaf->db, sql,
                                       collect_shared_to, &ret,
                                       2, "string", owner, "string", repo_id) < 0) {
        seaf_warning ("[share mgr] DB error when list shared to.\n");
        string_list_free (ret);
        return NULL;
    }

    return ret;
}

static gboolean
collect_repo_shared_to (SeafDBRow *row, void *data)
{
    GList **shared_to = data;
    const char *to_email = seaf_db_row_get_column_text (row, 0);
    char *email_down = g_ascii_strdown(to_email, -1);
    const char *perm = seaf_db_row_get_column_text (row, 1);
    const char *repo_id = seaf_db_row_get_column_text (row, 2);

    SeafileSharedUser *uobj = g_object_new (SEAFILE_TYPE_SHARED_USER,
                                            "repo_id", repo_id,
                                            "user", email_down,
                                            "perm", perm,
                                            NULL);
    *shared_to = g_list_prepend (*shared_to, uobj);
    g_free (email_down);

    return TRUE;
}

GList *
seaf_share_manager_list_repo_shared_to (SeafShareManager *mgr,
                                        const char *from_email,
                                        const char *repo_id,
                                        GError **error)
{
    GList *shared_to = NULL;
    char *sql = "SELECT to_email, permission, repo_id FROM SharedRepo WHERE "
                "from_email=? AND repo_id=?";

    int ret = seaf_db_statement_foreach_row (mgr->seaf->db, sql,
                                             collect_repo_shared_to, &shared_to,
                                             2, "string", from_email, "string", repo_id);
    if (ret < 0) {
        seaf_warning ("Failed to list repo %s shared to from db.\n", repo_id);
        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
                     "Failed to list repo shared to from db");
        while (shared_to) {
            g_object_unref (shared_to->data);
            shared_to = g_list_delete_link (shared_to, shared_to);
        }
        return NULL;
    }

    return shared_to;
}

static gboolean
collect_repo_shared_group (SeafDBRow *row, void *data)
{
    GList **shared_group = data;
    int group_id = seaf_db_row_get_column_int (row, 0);
    const char *perm = seaf_db_row_get_column_text (row, 1);
    const char *repo_id = seaf_db_row_get_column_text (row, 2);

    SeafileSharedGroup *gobj = g_object_new (SEAFILE_TYPE_SHARED_GROUP,
                                             "repo_id", repo_id,
                                             "group_id", group_id,
                                             "perm", perm,
                                             NULL);
    *shared_group = g_list_prepend (*shared_group, gobj);

    return TRUE;
}

GList *
seaf_share_manager_list_repo_shared_group (SeafShareManager *mgr,
                                           const char *from_email,
                                           const char *repo_id,
                                           GError **error)
{
    GList *shared_group = NULL;
    char *sql = "SELECT group_id, permission, repo_id FROM RepoGroup WHERE "
                "user_name=? AND repo_id=?";

    int ret = seaf_db_statement_foreach_row (mgr->seaf->db, sql,
                                             collect_repo_shared_group, &shared_group,
                                             2, "string", from_email, "string", repo_id);
    if (ret < 0) {
        seaf_warning ("Failed to list repo %s shared group from db.\n", repo_id);
        g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL,
                     "Failed to list repo shared group from db");
        while (shared_group) {
            g_object_unref (shared_group->data);
            shared_group = g_list_delete_link (shared_group, shared_group);
        }
        return NULL;
    }

    return shared_group;
}

static gboolean
get_shared_dirs_to_user (SeafDBRow *row, void *data)
{
    GHashTable *dirs = data;

    const char *path = seaf_db_row_get_column_text (row, 0);
    const char *perm = seaf_db_row_get_column_text (row, 1);
    g_hash_table_replace (dirs, g_strdup (path), g_strdup (perm));

    return TRUE;
}

static gboolean
get_shared_dirs_to_group (SeafDBRow *row, void *data)
{
    GHashTable *dirs = data;

    const char *path = seaf_db_row_get_column_text (row, 0);
    const char *perm = seaf_db_row_get_column_text (row, 1);

    char *prev_perm = g_hash_table_lookup (dirs, path);
    if (g_strcmp0 (perm, prev_perm) != 0 &&
        (prev_perm == NULL || g_strcmp0 (prev_perm, "r") == 0)) {
        g_hash_table_replace (dirs, g_strdup (path), g_strdup (perm));
    }

    return TRUE;
}

// Conver group id list to comma separated str
// [1, 2, 3] -> 1,2,3
static GString *
convert_group_list_to_str (GList *groups)
{
    GList *iter = groups;
    CcnetGroup *group;
    int group_id;
    GString *group_ids = g_string_new ("");

    for (; iter; iter = iter->next) {
        group = iter->data;
        g_object_get (group, "id", &group_id, NULL);
        g_string_append_printf (group_ids, "%d,", group_id);
    }
    group_ids = g_string_erase (group_ids, group_ids->len - 1, 1);

    return group_ids;
}

GHashTable *
seaf_share_manager_get_shared_dirs_to_user (SeafShareManager *mgr,
                                            const char *orig_repo_id,
                                            const char *to_email)
{
    GHashTable *dirs;
    char *sql;

    dirs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
    sql = "SELECT v.path, s.permission FROM SharedRepo s, VirtualRepo v WHERE "
          "s.repo_id = v.repo_id AND s.to_email = ? AND v.origin_repo = ?";

    int ret = seaf_db_statement_foreach_row (mgr->seaf->db, sql, get_shared_dirs_to_user,
                                             dirs, 2, "string", to_email,
                                             "string", orig_repo_id);
    if (ret < 0) {
        seaf_warning ("Failed to get all shared folder perms "
                      "in parent repo %.8s for user %s.\n", orig_repo_id, to_email);
        g_hash_table_destroy (dirs);
        return NULL;
    }

    return dirs;
}

GHashTable *
seaf_share_manager_get_shared_dirs_to_group (SeafShareManager *mgr,
                                             const char *orig_repo_id,
                                             GList *groups)
{
    GHashTable *dirs;
    GString *group_ids;
    char *sql;

    dirs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
    group_ids = convert_group_list_to_str (groups);
    sql = g_strdup_printf ("SELECT v.path, s.permission "
                           "FROM RepoGroup s, VirtualRepo v WHERE "
                           "s.repo_id = v.repo_id AND v.origin_repo = ? "
                           "AND s.group_id in (%s)", group_ids->str);

    int ret = seaf_db_statement_foreach_row (mgr->seaf->db, sql, get_shared_dirs_to_group,
                                             dirs, 1, "string", orig_repo_id);
    g_free (sql);
    g_string_free (group_ids, TRUE);

    if (ret < 0) {
        seaf_warning ("Failed to get all shared folder perm from parent repo %.8s "
                      "to all user groups.\n", orig_repo_id);
        g_hash_table_destroy (dirs);
        return NULL;
    }

    return dirs;
}

int
seaf_share_manager_remove_share (SeafShareManager *mgr, const char *repo_id,
                                 const char *from_email, const char *to_email)
{
    if (seaf_db_statement_query (mgr->seaf->db,
                       "DELETE FROM SharedRepo WHERE repo_id = ? AND from_email ="
                       " ? AND to_email = ?",
                       3, "string", repo_id, "string", from_email,
                       "string", to_email) < 0)
        return -1;

    return 0;
}

int
seaf_share_manager_unshare_subdir (SeafShareManager* mgr,
                                   const char *orig_repo_id,
                                   const char *path,
                                   const char *from_email,
                                   const char *to_email)
{
    if (seaf_db_statement_query (mgr->seaf->db,
                                 "DELETE FROM SharedRepo WHERE "
                                 "from_email = ? AND to_email = ? "
                                 "AND repo_id IN "
                                 "(SELECT repo_id FROM VirtualRepo WHERE "
                                 "origin_repo = ? AND path = ?)",
                                 4, "string", from_email,
                                 "string", to_email,
                                 "string", orig_repo_id,
                                 "string", path) < 0)
        return -1;

    return 0;
}

int
seaf_share_manager_remove_repo (SeafShareManager *mgr, const char *repo_id)
{
    if (seaf_db_statement_query (mgr->seaf->db,
                       "DELETE FROM SharedRepo WHERE repo_id = ?",
                       1, "string", repo_id) < 0)
        return -1;

    return 0;
}

char *
seaf_share_manager_check_permission (SeafShareManager *mgr,
                                     const char *repo_id,
                                     const char *email)
{
    char *sql;

    sql = "SELECT permission FROM SharedRepo WHERE repo_id=? AND to_email=?";
    return seaf_db_statement_get_string (mgr->seaf->db, sql,
                                         2, "string", repo_id, "string", email);
}

static gboolean
get_shared_sub_dirs (SeafDBRow *row, void *data)
{
    GHashTable *sub_dirs = data;
    int dummy;

    const char *sub_dir = seaf_db_row_get_column_text (row, 0);
    g_hash_table_replace (sub_dirs, g_strdup(sub_dir), &dummy);

    return TRUE;
}

GHashTable *
seaf_share_manager_get_shared_sub_dirs (SeafShareManager *mgr,
                                        const char *repo_id,
                                        const char *path)
{
    GHashTable *sub_dirs = g_hash_table_new_full (g_str_hash, g_str_equal,
                                                  g_free, NULL);
    char *pattern;
    if (strcmp (path, "/") == 0) {
        pattern = g_strdup_printf("%s%%", path);
    } else {
        pattern = g_strdup_printf ("%s/%%", path);
    }
    int ret = seaf_db_statement_foreach_row (mgr->seaf->db,
                                             "SELECT v.path FROM VirtualRepo v, SharedRepo s "
                                             "WHERE v.repo_id = s.repo_id and "
                                             "v.origin_repo = ? AND v.path LIKE ?",
                                             get_shared_sub_dirs, sub_dirs,
                                             2, "string", repo_id, "string", pattern);

    if (ret < 0) {
        g_free (pattern);
        seaf_warning ("Failed to get shared sub dirs from db.\n");
        g_hash_table_destroy (sub_dirs);
        return NULL;
    }

    ret = seaf_db_statement_foreach_row (mgr->seaf->db,
                                         "SELECT v.path FROM VirtualRepo v, RepoGroup r "
                                         "WHERE v.repo_id = r.repo_id and "
                                         "v.origin_repo = ? AND v.path LIKE ?",
                                         get_shared_sub_dirs, sub_dirs,
                                         2, "string", repo_id, "string", pattern);
    g_free (pattern);

    if (ret < 0) {
        seaf_warning ("Failed to get shared sub dirs from db.\n");
        g_hash_table_destroy (sub_dirs);
        return NULL;
    }

    return sub_dirs;
}

int
seaf_share_manager_is_repo_shared (SeafShareManager *mgr,
                                   const char *repo_id)
{
    gboolean ret;
    gboolean db_err = FALSE;

    ret = seaf_db_statement_exists (mgr->seaf->db,
                                    "SELECT repo_id FROM SharedRepo WHERE "
                                    "repo_id = ?", &db_err,
                                    1, "string", repo_id);
    if (db_err) {
        seaf_warning ("DB error when check repo exist in SharedRepo.\n");
        return -1;
    }

    if (!ret) {
        ret = seaf_db_statement_exists (mgr->seaf->db,
                                        "SELECT repo_id FROM RepoGroup WHERE "
                                        "repo_id = ?", &db_err,
                                        1, "string", repo_id);
        if (db_err) {
            seaf_warning ("DB error when check repo exist in RepoGroup.\n");
            return -1;
        }
    }

    return ret;
}

GObject *
seaf_get_shared_repo_by_path (SeafRepoManager *mgr,
                              const char *repo_id,
                              const char *path,
                              const char *shared_to,
                              int is_org,
                              GError **error)
{
    char *sql;
    char *real_repo_id = NULL;
    GList *repo = NULL;
    GObject *ret = NULL;

    /* If path is NULL, 'repo_id' represents for the repo we want,
     * otherwise, 'repo_id' represents for the origin repo,
     * find virtual repo by path first.
     */
    if (path != NULL) {
        real_repo_id = seaf_repo_manager_get_virtual_repo_id (mgr, repo_id, path, NULL);
        if (!real_repo_id) {
            seaf_warning ("Failed to get virtual repo_id by path %s, origin_repo: %s\n", path, repo_id);
            return NULL;
        }
    }
    if (!real_repo_id)
        real_repo_id = g_strdup (repo_id);

    if (!is_org)
        sql = "SELECT sh.repo_id, v.repo_id, "
              "from_email, permission, commit_id, s.size, "
              "v.origin_repo, v.path, i.name, "
              "i.update_time, i.version, i.is_encrypted, i.last_modifier, i.status, i.type, "
              "i2.name FROM "
              "SharedRepo sh LEFT JOIN VirtualRepo v ON "
              "sh.repo_id=v.repo_id "
              "LEFT JOIN RepoSize s ON sh.repo_id = s.repo_id "
              "LEFT JOIN RepoInfo i ON sh.repo_id = i.repo_id "
              "LEFT JOIN RepoInfo i2 ON v.origin_repo = i2.repo_id, Branch b "
              "WHERE to_email=? AND "
              "sh.repo_id = b.repo_id AND sh.repo_id=? AND "
              "b.name = 'master' ";
    else
        sql = "SELECT sh.repo_id, v.repo_id, "
              "from_email, permission, commit_id, s.size, "
              "v.origin_repo, v.path, i.name, "
              "i.update_time, i.version, i.is_encrypted, i.last_modifier, i.status, i.type, "
              "i2.name FROM "
              "OrgSharedRepo sh LEFT JOIN VirtualRepo v ON "
              "sh.repo_id=v.repo_id "
              "LEFT JOIN RepoSize s ON sh.repo_id = s.repo_id "
              "LEFT JOIN RepoInfo i ON sh.repo_id = i.repo_id "
              "LEFT JOIN RepoInfo i2 ON v.origin_repo = i2.repo_id, Branch b "
              "WHERE to_email=? AND "
              "sh.repo_id = b.repo_id AND sh.repo_id=? AND "
              "b.name = 'master' ";

    /* The list 'repo' should have only one repo,
     * use existing api collect_repos() to get it.
     */
    if (seaf_db_statement_foreach_row (mgr->seaf->db, sql,
                                       collect_repos, &repo,
                                       2, "string", shared_to, "string", real_repo_id) < 0) {
            g_free (real_repo_id);
            g_list_free (repo);
            seaf_warning ("[share mgr] DB error when get shared repo "
                          "for %s, path:%s\n", shared_to, path);
            return NULL;
    }
    g_free (real_repo_id);
    if (repo) {
        ret = (GObject *)(repo->data);
        g_list_free (repo);
    }

    return ret;
}

int
seaf_share_manager_unshare_group_subdir (SeafShareManager* mgr,
                                         const char *repo_id,
                                         const char *path,
                                         const char *owner,
                                         int group_id)
{
    if (seaf_db_statement_query (mgr->seaf->db,
                                 "DELETE FROM RepoGroup WHERE "
                                 "user_name = ? AND group_id = ? "
                                 "AND repo_id IN "
                                 "(SELECT repo_id FROM VirtualRepo WHERE "
                                 "origin_repo = ? AND path = ?)",
                                 4, "string", owner,
                                 "int", group_id,
                                 "string", repo_id,
                                 "string", path) < 0)
        return -1;

    return 0;
}

gboolean
seaf_share_manager_repo_has_been_shared (SeafShareManager* mgr,
                                         const char *repo_id,
                                         gboolean including_groups)
{
    gboolean exists;
    gboolean db_err = FALSE;
    char *sql;

    sql = "SELECT 1 FROM SharedRepo WHERE repo_id=?";
    exists = seaf_db_statement_exists (mgr->seaf->db, sql, &db_err,
                                       1, "string", repo_id);
    if (db_err) {
        seaf_warning ("DB error when check repo exist in SharedRepo and RepoGroup.\n");
        return FALSE;
    }

    if (!exists && including_groups) {
        sql = "SELECT 1 FROM RepoGroup WHERE repo_id=?";
        exists = seaf_db_statement_exists (mgr->seaf->db, sql, &db_err,
                                           1, "string", repo_id);
    }

    return exists;
}

gboolean
get_shared_users_cb (SeafDBRow *row, void *data)
{
    GList **users = data;
    const char *repo_id = seaf_db_row_get_column_text (row, 0);
    const char *user = seaf_db_row_get_column_text (row, 1);
    const char *perm = seaf_db_row_get_column_text (row, 2);
    SeafileSharedUser *uobj = g_object_new (SEAFILE_TYPE_SHARED_USER,
                                            "repo_id", repo_id,
                                            "user", user,
                                            "perm", perm,
                                            NULL);
    *users = g_list_append (*users, uobj);

    return TRUE;
}

GList *
seaf_share_manager_org_get_shared_users_by_repo (SeafShareManager* mgr,
                                                 int org_id,
                                                 const char *repo_id)
{
    GList *users = NULL;
    char *sql = "SELECT repo_id, to_email, permission FROM OrgSharedRepo WHERE org_id=? AND "
                "repo_id=?";

    int ret = seaf_db_statement_foreach_row (mgr->seaf->db, sql,
                                             get_shared_users_cb, &users,
                                             2, "int", org_id, "string", repo_id);
    if (ret < 0) {
        seaf_warning("Failed to get users by repo_id[%s], org_id[%d]\n",
                     repo_id, org_id);
        return NULL;
    }

    return users;
}


GList *
seaf_share_manager_get_shared_users_by_repo(SeafShareManager* mgr,
                                            const char *repo_id)
{
    GList *users = NULL;
    char *sql = "SELECT repo_id, to_email, permission FROM SharedRepo WHERE "
                "repo_id=?";

    int ret = seaf_db_statement_foreach_row (mgr->seaf->db, sql,
                                             get_shared_users_cb, &users,
                                             1, "string", repo_id);
    if (ret < 0) {
        seaf_warning("Failed to get users by repo_id[%s]\n", repo_id);
        return NULL;
    }

    return users;
}
