/* which witch is which?
 *
 *   witch - the rich implementation of which
 * 
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Copyright (c) 2007, William A. Rowe, Jr.
 *
 * 0.1.0 - initial design
 * 0.1.1 - minor win32 compilation fixes
 */

#include <apr.h>
#include <apr_env.h>
#include <apr_tables.h>
#include <apr_getopt.h>
#include <apr_strings.h>
#include <apr_file_info.h>
#include <stdio.h>

#define APP_VERSION \
"witch v0.1.1 Copyright (c) 2007, William A. Rowe, Jr.\n" \
"Licensed by one or more contributors under the Apache Software License v2.0\n"

#if defined(WIN32) || defined(OS2)
#define APP_HELP APP_VERSION \
"Syntax: %s [-afcxrv?] [-p PATHS] [-d DELPATHS] [-e EXTS] files ...\n" \
"   -a\tall matches\n" \
"   -f\tplain files (doesn't test exec flag, no default -e value)\n" \
"   -c\tinclude cwd (default for %PATH% search, when -p is not specified)\n" \
"   -x\texclude cwd (default if -p is specified)\n" \
"   -r\trelative paths (default behavior expands full path names)\n" \
"   -v\tprogram version\n" \
"   -?\tthis message\n" \
"   -p PATHS\tlist to search (';' delimeted, default is %PATH%)\n" \
"   -d DELPATHS\tlist to omit (';' delimiteded)\n" \
"   -e EXTLIST\textensions to match (';' delimited, default is %PATHEXT%)\n"
#else
#define APP_HELP APP_VERSION \
"Syntax: %s [-afcxrv?] [-p PATHS] [-d DELPATHS] [-e EXTS] files ...\n" \
"   -a\tall matches\n" \
"   -f\tplain files (doesn't test exec flag, no default -e value)\n" \
"   -c\tinclude cwd (prior to first directory of -p or $PATH)\n" \
"   -x\texclude cwd (default)\n" \
"   -r\trelative paths (default behavior expands full path names)\n" \
"   -v\tprogram version\n" \
"   -?\tthis message\n" \
"   -p PATHS\tlist to search (':' delimited, default is $PATH)\n" \
"   -d DELPATHS\tlist to omit (':' delimited)\n" \
"   -e EXTLIST\textensions to match (':' delimited, implies -f)\n"
#endif

#define EXECUTE (APR_FPROT_UEXECUTE | APR_FPROT_GEXECUTE | APR_FPROT_WEXECUTE)

int main (int argc, char const* const* argv, char const* const* env)
{
    apr_status_t rv;
    apr_pool_t *toppool, *tmppool;
    apr_getopt_t *opts;
    char argch;
    const char *argval, **elt;
    int allmatches = 0, plainfiles = 0;
    int pathrooted = 1, currentdir = -1;
    int seenexts = 0, seenpaths = 0;
    apr_array_header_t *array, *paths, *exts;
    char **path, **ext, *name;
    int x, y, found;
    apr_finfo_t finfo;

    rv = apr_app_initialize(&argc, &argv, &env);
    atexit(apr_terminate);
    rv = apr_pool_create_ex(&toppool, NULL, NULL, NULL);
    rv = apr_pool_create_ex(&tmppool, toppool, NULL, NULL);

    paths = apr_array_make(toppool, 0, sizeof(char*));
    exts = apr_array_make(toppool, 0, sizeof(char*));

    rv = apr_getopt_init(&opts, toppool, argc, argv);

    while ((rv = apr_getopt(opts, "afcxrv?p:d:e:", &argch, &argval))
               == APR_SUCCESS) {
        switch (argch)
        {
        case 'a':  /* all */
            allmatches = 1;
            break;
        case 'f':  /* files (non-exec) */
            plainfiles = 1;
            break;
        case 'c':  /* include current */
            if (currentdir >= 0) {
                goto outch;
            }
            currentdir = 1;
            break;
        case 'x':  /* include current */
            if (currentdir >= 0) {
                goto outch;
            }
            currentdir = 0;
            break;
        case 'r':  /* relative file paths */
            pathrooted = 0;
            break;
        case 'p':  /* paths */
            if (currentdir < 0)
                currentdir = 0;
            if (currentdir) {
                *(char const**)apr_array_push(paths) = ".";
                currentdir = 0;
            }
            rv = apr_filepath_list_split(&array, argval, toppool);
            apr_array_cat(paths, array);
            seenpaths = 1;
            break;
        case 'd':  /* deletepaths */
            if (!seenpaths) {
                rv = apr_env_get((char**)&argval, "PATH", toppool);
#if defined(WIN32) || defined(OS2)
                if (currentdir != 0) {
                    currentdir = 1;
#else
                if (currentdir == 1) {
#endif
                    elt = apr_array_push(paths);
                    *elt = ".";
                }
                rv = apr_filepath_list_split(&array, argval, toppool);
                apr_array_cat(paths, array);
                seenpaths = 1;
            }
            rv = apr_filepath_list_split(&array, argval, tmppool);
            /* XXX TODO - remove elts of array from array 'paths' */
            apr_pool_clear(tmppool);
            break;
        case 'e':  /* exts */
            plainfiles = 1;
            rv = apr_filepath_list_split(&array, argval, toppool);
            if (!seenexts && ((*argval == ';') || (*argval == ':')))
                *(char const**)apr_array_push(exts) = "";
            apr_array_cat(exts, array);
            seenexts = 1;
            break;
        case 'v':  /* version */
            fputs(APP_VERSION, stdout);
            return 0;
        case '?':  /* help */
            fprintf(stdout, APP_HELP, argv[0]);
            return 0;
        otherwise:
            goto outch;
        }
    }

    if ((rv != APR_EOF) || ((opts->ind >= opts->argc))) {
outch:
        fprintf(stderr, APP_HELP, argv[0]);
        return 255;
    }

    if (!seenpaths) {
        rv = apr_env_get((char**)&argval, "PATH", toppool);
#if defined(WIN32) || defined(OS2)
        if (currentdir != 0) {
            currentdir = 1;
#else
        if (currentdir == 1) {
#endif
            elt = apr_array_push(paths);
            *elt = ".";
        }
        rv = apr_filepath_list_split(&array, argval, toppool);
        apr_array_cat(paths, array);
        apr_pool_clear(tmppool);
    }

    if (!seenexts) {
#if defined(WIN32) || defined(OS2)
        if (!plainfiles) {
            rv = apr_env_get((char**)&argval, "PATHEXT", toppool);
            rv = apr_filepath_list_split(&exts, argval, toppool);
            apr_pool_clear(tmppool);
        }
        else
#endif
            *(char const**)apr_array_push(exts) = "";
    }

    for (path = (void*)paths->elts, x = 0; x < paths->nelts; ++path, ++x)
    {
        rv = apr_filepath_merge(path, pathrooted ? NULL : "", *path, 
                                APR_FILEPATH_NATIVE, toppool);
    }

    while (opts->ind < opts->argc)
    {
        argval = opts->argv[opts->ind];
        ++opts->ind;
        path = (void*)paths->elts;
        found = 0;
        for (x = 0; x < paths->nelts && (allmatches || !found); ++path, ++x)
        {
            ext = (void*)exts->elts;
            for (y = 0; y < exts->nelts && (allmatches || !found); ++ext, ++y)
            {
                name = apr_pstrcat(tmppool, argval, *ext, NULL);
                rv = apr_filepath_merge(&name, *path, name,
                                        APR_FILEPATH_NATIVE, tmppool);
                rv = apr_stat(&finfo, name,
                              plainfiles ? APR_FINFO_MIN
                                         : APR_FINFO_MIN | APR_FINFO_PROT,
                              tmppool);
                if (!rv && (finfo.filetype == APR_REG))
                {
                    if (!plainfiles && !(finfo.protection & EXECUTE))
                        continue;
                    fputs(apr_pstrcat(tmppool, name, "\n", NULL), stdout);
                    found = 1;
                }
            }
            apr_pool_clear(tmppool);
        }
        if (!found) {
            char *pathlist;
            rv = apr_filepath_list_merge(&pathlist, paths, tmppool);
            if ((exts->nelts > 1) || ((char**)exts->elts)[0][0]) {
                char *extlist;
                rv = apr_filepath_list_merge(&extlist, exts, tmppool);
                fputs(apr_pstrcat(tmppool, argv[0], ": no ", argval, 
                                  " of (", extlist, ") in (", pathlist,
                                  ")\n", NULL), stdout);
            }
            else
                fputs(apr_pstrcat(tmppool, argv[0], ": no ", argval, 
                                  " in (", pathlist, ")\n", NULL), stdout);
            apr_pool_clear(tmppool);
        } 
    } 

    return 0;
}


