/*
 * %CopyrightBegin%
 * 
 * Copyright Ericsson AB 2009. All Rights Reserved.
 * 
 * The contents of this file are subject to the Erlang Public License,
 * Version 1.1, (the "License"); you may not use this file except in
 * compliance with the License. You should have received a copy of the
 * Erlang Public License along with this software. If not, it can be
 * retrieved online at http://www.erlang.org/.
 * 
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * %CopyrightEnd%
 */

/*
 * This is a C version of the erl Bourne shell script
 */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include "sys.h"
#include <stdlib.h>
#include <stdarg.h>

#define BOOL int
#define TRUE 1
#define FALSE 0
#define PATHSEP ":"
#define DIRSEP "/"
#define DIRSEPCHAR '/'

static void
error(char* format, ...)
{
    char sbuf[1024];
    va_list ap;

    va_start(ap, format);
    vsprintf(sbuf, format, ap);
    va_end(ap);
    fprintf(stderr, "erl: %s\n", sbuf);
    exit(1);
}

/*
 * Variables.
 */

/*
 * Manage memory
 */

static void *
emalloc(size_t size)
{
    void *p = malloc(size);
    if (p == NULL)
        error("Insufficient memory");
    return p;
}

/*
static void *
erealloc(void *p, size_t size)
{
    void *res = realloc(p, size);
    if (res == NULL)
        error("Insufficient memory");
    return res;
}
*/

static void
efree(void *p)
{
    free(p);
}

static char*
strsave(char* string)
{
    char* p = emalloc(strlen(string)+1);
    strcpy(p, string);
    return p;
}

/*
 * Manage environment variables
 */

static char *
get_env(char *key)
{
    return getenv(key);
}

static void
set_env(char *key, char *value)
{
    size_t size = strlen(key) + 1 + strlen(value) + 1;
    char *str = emalloc(size);
    sprintf(str, "%s=%s", key, value);
    if (putenv(str) != 0)
        error("putenv(\"%s\") failed!", str);
#ifdef HAVE_COPYING_PUTENV
    efree(str);
#endif
}

// /* A realpath look alike */
// static char *
// follow_symlinks(const char *path, char *resolved_path)
// {
//     char tmp[PATH_MAX];
//     int  len; 
// 
//     strcpy(resolved_path, path);
//     
//     for (;;) {
// 	len = readlink(resolved_path, tmp, PATH_MAX);
// 
// 	if (len == -1) {
// 	    if (errno == EINVAL) {
// 		/* Not a symbolic link. use the original name */
// 		break;
// 	    } else {
// 		return NULL;
// 	    }
// 	} else {
// 	    tmp[len] = '\0';
// 	    strcpy(resolved_path, tmp);
// 	}
//     }
//     
//     return resolved_path;
// }

/*
 * Find absolute path to this program
 */

static char *
find_prog(char *origpath)
{
    char relpath[PATH_MAX];
    char abspath[PATH_MAX];

    strcpy(relpath, origpath);

    if (strstr(relpath, DIRSEP) == NULL) {
        /* Just a base name */
        char *envpath;

        envpath = get_env("PATH");
        if (envpath) {
            /* Try to find the executable in the path */
            char dir[PATH_MAX];
            char *beg = envpath;
            char *end;
            int sz;
            DIR *dp;             /* Pointer to directory structure. */
            struct dirent* dirp; /* Pointer to directory entry.     */
            BOOL look_for_sep = TRUE;

            while (look_for_sep) {
                end = strstr(beg, PATHSEP);
                if (end != NULL) {
                    sz = end - beg;
                    strncpy(dir, beg, sz);
                    dir[sz] = '\0';
                } else {
                    sz = strlen(beg);
                    strcpy(dir, beg);
                    look_for_sep = FALSE;
                }
                beg = end + 1;

                dp = opendir(dir);
                if (dp != NULL) {
                    while (TRUE) {
                        dirp = readdir(dp);
                        if (dirp == NULL) {
                            closedir(dp);
                            /* Try next directory in path */
                            break;
                        }

                        if (strcmp(origpath, dirp->d_name) == 0) {
                            /* Wow. We found the executable. */
                            strcpy(relpath, dir);
                            strcat(relpath, DIRSEP);
                            strcat(relpath, dirp->d_name);
                            closedir(dp);
                            look_for_sep = FALSE;
                            break;
                        }
                    }
                }
            }
        }
    }

    if (!realpath(relpath, abspath)) {
        error("Cannot determine real path to erl");
    }

    return strdup(abspath);
}

/*
 * Find bindir
 */

static void
copy_latest_vsn(char *latest_vsn, char *next_vsn)
{
    char *lp;
    char *np;
    BOOL greater;

    /* Find vsn */
    for (lp = latest_vsn+strlen(latest_vsn)-1 ;lp > latest_vsn && *lp != DIRSEPCHAR; --lp)
	;
	
    /* lp =+ length("erts-"); */
    for (np = next_vsn+strlen(next_vsn)-1 ;np > next_vsn && *np != DIRSEPCHAR; --np)
       ;

    /* np =+ length("erts-"); */
    while (TRUE) {
        if (*lp != *np) {
	    if (*np > *lp) {
		greater = TRUE;
	    } else {
		greater = FALSE;
	    }
	    
	    /* Find next dot or eos */
	    while (*lp != '\0' && *np != '\0') {
		lp++;
		np++;
		if (*np == '.' && *lp == '.') {
		    break;
		}
		if (*np == '\0' && *lp == '\0') {
		    break;
		}
		if (*lp == '.' || *lp == '\0') {
  		    greater = TRUE;
		}
		if (*np == '.' || *np == '\0') {
		    greater = FALSE;
		}
	    }
	    if (greater) {
		strcpy(latest_vsn, next_vsn);
	    }
	    return;
	}
	++lp;
	++np;
    }
}

static char *
find_erts_vsn(char *erl_top)
{
    /* List install dir and look for latest erts-vsn */
    DIR *dp;                    /* Pointer to directory structure. */
    struct dirent* dirp;        /* Pointer to directory entry.     */
    char latest_vsn[PATH_MAX];  /* Latest erts-vsn directory name. */

    dp = opendir(erl_top);
    if (dp == NULL) {
        return NULL;
    }

    latest_vsn[0] = '\0';
    for (;;) {
        dirp = readdir(dp);
        if (dirp == NULL) {
            closedir(dp);
            break;
        }
        if (strncmp("erts-", dirp->d_name, 5) == 0) {
	    copy_latest_vsn(latest_vsn, dirp->d_name);
        }
    }

    if (latest_vsn[0] == '\0') {
        return NULL;
    } else {
	char *p = malloc((strlen(erl_top)+1+strlen(latest_vsn)+4+1)*sizeof(char));
        strcpy(p,erl_top);
        strcat(p,DIRSEP);
        strcat(p,latest_vsn);
        strcat(p,DIRSEP);
        strcat(p,"bin");
        return p;
    }
}

static char *
find_bindir(char *erlpath)
{
    /* Assume that the path to erl is absolute and
     * that it is not a symbolic link*/

    char *p;
    char *p2;
    char buffer[PATH_MAX];

    strcpy(buffer, erlpath);

    /* Chop of base name*/
    for (p = buffer+strlen(buffer)-1 ;p >= buffer && *p != DIRSEPCHAR; --p)
        ;
    *p = '\0';
    p--;

    /* Check if dir path is like ...\buffer\erts-vsn\bin */
    for (;p >= buffer && *p != DIRSEPCHAR; --p)
        ;
    p--;
    for (p2 = p;p2 >= buffer && *p2 != DIRSEPCHAR; --p2)
        ;
    p2++;
    if (strncmp(p2, "erts-", 5) == 0) {
	p = strsave(buffer);
	return p;
    }

    /* Assume that dir path is like ...\buffer\bin */
    *++p ='\0'; /* chop off bin dir */

    p = find_erts_vsn(buffer);
    if (p == NULL) {
	return strsave(buffer);
    } else {
	return p;
    }
}

/*
 * main
 */

int
main(int argc, char **argv)
{
    char *p;
    char *abspath;
    char *bindir;            /* Location of executables. */
    char rootdir[PATH_MAX];  /* Root location of Erlang installation. */
    char progname[PATH_MAX]; /* Name of this program. */
    char erlexec[PATH_MAX];  /* Path to erlexec */

    /* Determine progname */
    abspath = find_prog(argv[0]);
    strcpy(progname, abspath);
    for (p = progname+strlen(progname)-1;p >= progname && *p != '/'; --p)
	;

    /* Determine bindir */
    bindir = find_bindir(abspath);

    /* Determine rootdir */
    strcpy(rootdir, bindir);
    for (p = rootdir+strlen(rootdir)-1;p >= rootdir && *p != '/'; --p)
	;
    p--;
    for (;p >= rootdir && *p != '/'; --p)
	;
    *p ='\0';

    /* Update environment */
    set_env("EMU", "beam");
    set_env("PROGNAME", progname);
    set_env("BINDIR", bindir);
    set_env("ROOTDIR", rootdir);

    /* Invoke erlexec */
    strcpy(erlexec, bindir);
    strcat(erlexec, DIRSEP);
    strcat(erlexec, "erlexec");

    efree(abspath);
    efree(bindir);

    execvp(erlexec, argv);
    error("Error %d executing \'%s\'.", errno, erlexec);
    return 2;
}