/*!
   \file lib/gis/find_file.c

   \brief GIS library - Find GRASS data base files

   (C) 2001-2009 by the GRASS Development Team

   This program is free software under the
   GNU General Public License (>=v2).
   Read the file COPYING that comes with GRASS
   for details.

   \author Original author CERL
 */

#include <string.h>
#include <unistd.h>
#include <grass/gis.h>
#include <grass/glocale.h>

static const char *find_element(int misc, const char *dir, const char *element)
{
    static const char *cell_elements[] = {"cellhd", "cell",    "cats",
                                          "colr",   "hist",    "cell_misc",
                                          "fcell",  "g3dcell", NULL};
    static const char *dig_elements[] = {
        "dig", "dig_att", "dig_plus", "dig_cats", "dig_misc", "reg", NULL};
    const char *search = misc ? dir : element;
    int i;

    for (i = 1; cell_elements[i]; i++)
        if (strcmp(search, cell_elements[i]) == 0)
            return cell_elements[0];
    for (i = 1; dig_elements[i]; i++)
        if (strcmp(search, dig_elements[i]) == 0)
            return dig_elements[0];
    return element;
}

static const char *find_file(int misc, const char *dir, const char *element,
                             const char *name, const char *mapset)
{
    char path[GPATH_MAX];
    char xname[GNAME_MAX], xmapset[GMAPSET_MAX];
    const char *pname, *pmapset;
    int n;

    if (*name == 0)
        return NULL;
    *path = 0;

    /*
     * if name is in the fully qualified format, split it into
     * name, mapset (overrides what was in mapset)
     */
    if (G_name_is_fully_qualified(name, xname, xmapset)) {
        pname = xname;
        pmapset = xmapset;
    }
    else {
        pname = name;
        pmapset = mapset;
    }

    if (strcmp(element, "vector") == 0 && pmapset &&
        strcasecmp(pmapset, "ogr") == 0) {
        /* don't check for virtual OGR mapset */
        return G_store(pmapset);
    }

    /*
     * reject illegal names and mapsets
     */
    if (G_legal_filename(pname) == -1)
        return NULL;

    if (pmapset && *pmapset && G_legal_filename(pmapset) == -1)
        return NULL;

    /*
     * if no specific mapset is to be searched
     * then search all mapsets in the mapset search list
     */
    if (pmapset == NULL || *pmapset == 0) {
        int cnt = 0;
        const char *pselmapset = NULL;
        const char *pelement = find_element(misc, dir, element);

        for (n = 0; (pmapset = G_get_mapset_name(n)); n++) {
            if (misc && element == pelement)
                G_file_name_misc(path, dir, pelement, pname, pmapset);
            else
                G_file_name(path, pelement, pname, pmapset);
            if (access(path, 0) == 0) {
                if (!pselmapset)
                    pselmapset = pmapset;
                else if (element == pelement)
                    G_important_message(_("Data element '%s/%s' was found in "
                                          "more mapsets (also found in <%s>)"),
                                        element, pname, pmapset);
                cnt++;
            }
        }
        if (cnt > 0) {
            if (misc)
                G_file_name_misc(path, dir, element, pname, pselmapset);
            else
                G_file_name(path, element, name, pselmapset);
            if (access(path, 0) == 0) {
                /* If the same name exists in more mapsets and print a warning
                 */
                if (cnt > 1 && element == pelement)
                    G_important_message(_("Using <%s@%s>..."), pname,
                                        pselmapset);

                return G_store(pselmapset);
            }
        }
    }
    /*
     * otherwise just look for the file in the specified mapset.
     * since the name may have been qualified, mapset may point
     * to the xmapset, so we must should it to
     * permanent storage via G_store().
     */
    else {
        if (misc)
            G_file_name_misc(path, dir, element, pname, pmapset);
        else
            G_file_name(path, element, pname, pmapset);

        if (access(path, 0) == 0)
            return G_store(pmapset);
    }

    return NULL;
}

static const char *find_file1(int misc, const char *dir, const char *element,
                              char *name, const char *mapset)
{
    char xname[GNAME_MAX], xmapset[GMAPSET_MAX];
    const char *pname, *pmapset;
    const char *mp;

    if (G_name_is_fully_qualified(name, xname, xmapset)) {
        pname = xname;
        pmapset = xmapset;
    }
    else {
        pname = name;
        pmapset = mapset;
    }

    mp = find_file(misc, dir, element, pname, pmapset);

    if (mp && name != pname)
        strcpy(name, pname);

    return mp;
}

/*!
 * \brief Searches for a file from the mapset search list or in a
 * specified mapset.
 *
 * Returns the mapset name where the file was found.
 *
 * If the user specifies a fully qualified element (name\@mapset)
 * which exists, then G_find_file() modifies "name"
 * by removing the "@mapset" part.
 *
 * Rejects all names that begin with "."
 *
 * If <i>name</i> is of the form nnn in ppp then only mapset ppp
 * is searched.
 *
 * \param element database element (eg, "cell", "cellhd", "colr", etc)
 * \param name    file name to look for
 * \param mapset  mapset to search. if mapset is "" will search in mapset search
 * list
 *
 * \return pointer to a string with name of mapset where file was
 * found, or NULL if not found
 */
const char *G_find_file(const char *element, char *name, const char *mapset)
{
    return find_file1(0, NULL, element, name, mapset);
}

/*!
 * \brief Searches for a misc file from the mapset search list or in a
 * specified mapset.
 *
 * Returns the mapset name where the misc file was found.
 * Paths to misc files currently follow structure:
 * mapset/dir/name/element
 *
 * \param dir     file directory
 * \param element database element (eg, "cell", "cellhd", "colr", etc)
 * \param name    file name to look for
 * \param mapset  mapset to search. if mapset is "" will search in mapset search
 * list
 *
 * \return pointer to a string with name of mapset where file was
 * found, or NULL if not found
 */
const char *G_find_file_misc(const char *dir, const char *element, char *name,
                             const char *mapset)
{
    return find_file1(1, dir, element, name, mapset);
}

/*!
 * \brief Searches for a file from the mapset search list or in a
 * specified mapset. (look but don't touch)
 *
 * Returns the mapset name where the file was found.
 *
 * Exactly the same as G_find_file() except that if <i>name</i> is in
 * the form "<i>name@mapset</i>", and is found, G_find_file2() will
 * not alter <i>name</i> by removing the "@<i>mapset</i>" part.
 *
 * Rejects all names that begin with "."
 *
 * \param element    database element (eg, "cell", "cellhd", "colr", etc)
 * \param name       file name to look for
 * \param mapset     mapset to search. if mapset is "" will search in mapset
 * search list
 *
 * \return pointer to a string with name of mapset where file was
 * found, or NULL if not found
 */
const char *G_find_file2(const char *element, const char *name,
                         const char *mapset)
{
    return find_file(0, NULL, element, name, mapset);
}

/*!
 * \brief Searches for a misc file from the mapset search list or in a
 * specified mapset. (look but don't touch)
 *
 * Returns the mapset name where the misc file was found.
 * Paths to misc files currently follow structure:
 * mapset/dir/name/element
 *
 * \param dir        file directory
 * \param element    database element (eg, "cell", "cellhd", "colr", etc)
 * \param name       file name to look for
 * \param mapset     mapset to search. if mapset is "" will search in mapset
 * search list
 *
 * \return pointer to a string with name of mapset where file was
 * found, or NULL if not found
 */
const char *G_find_file2_misc(const char *dir, const char *element,
                              const char *name, const char *mapset)
{
    return find_file(1, dir, element, name, mapset);
}
