/*
 * %CopyrightBegin%
 * 
 * Copyright Ericsson AB 2008-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%
 */

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>
#include <dirent.h>
#include <sys/cygwin.h>

    

#ifdef CCP_POSIX_TO_WIN_A
#define NEW_CYGPATH_INTERFACE
#endif

#ifdef NEW_CYGPATH_INTERFACE
#define GET_WIN32_SIZE(Posix) \
cygwin_conv_path (CCP_POSIX_TO_WIN_A | CCP_ABSOLUTE, (Posix), NULL, 0)
#define CONVERT_TO_WIN32(Posix,Win32,Size) \
cygwin_conv_path (CCP_POSIX_TO_WIN_A | CCP_ABSOLUTE, (Posix), \
		  (Win32), (Size))
#else
#define GET_WIN32_SIZE(Posix) PATH_MAX
#define CONVERT_TO_WIN32(Posix,Win32,Size) \
((cygwin32_conv_to_full_win32_path((Posix),(Win32)) >= 0) ? 0 : -1)
#endif

/*#define HARDDEBUG 1*/

#ifdef HARDDEBUG
#define DEBUGF(X) printf X
#else
#define DEBUGF(X) /* noop */
#endif
char *tmpobjdir = "";

char *add_to(char *src,char *add) {
    int len = strlen(src)+strlen(add)+1;
    char *n;

    if (strlen(src) == 0) {
	n = malloc(len);
	strcpy(n,add);
	return n;
    }
    n = realloc(src,len);
    strcat(n,add);
    return n;
}

void error(char *str) 
{
    fprintf(stderr,"%s\n",str);
    exit(1);
}


char *dyn_get_short(char *longp) 
{
    int size;
    char *shortp;
    size = GetShortPathName(longp,NULL,0);
    if (size <= 0) {
	return NULL;
    }
    shortp = malloc(size);
    if (GetShortPathName(longp,shortp,size) != size - 1) {
	free(shortp);
	return NULL;
    }
    return shortp;
}

char *do_cyp(char *posix)
{
    ssize_t size;
    char *win32;
    size = GET_WIN32_SIZE(posix);
    char *ret = NULL;
    if (size < 0) {
	fprintf(stderr,"Could not cygpath %s, errno = %d\n",
		posix,errno);
    } else {
	win32 = (char *) malloc (size);
	if (CONVERT_TO_WIN32(posix,
			     win32, size)) {
	    fprintf(stderr,"Could not cygpath %s, errno = %d\n",
		    posix,errno);
	} else {
	    char *w32_short = dyn_get_short(win32);
	    DEBUGF(("win32 = %s, w32_short = %s\n",win32, (w32_short == NULL) ? "NULL" : w32_short));
	    if (w32_short == NULL) {
		char *rest = malloc(size);
		char *first = malloc(size);
		int x = 0;
		int y = strlen(win32) - 1;
		strcpy(first,win32);
		while (w32_short == NULL) {
		    while ( y > 0 && first[y] != '\\') {
			rest[x++] = first[y--];
		    }
		    if (y > 0) {
			rest[x++] = first[y];
			first[y--] = '\0'; 
		    } else {
			break;
		    }
		    w32_short = dyn_get_short(first);
		    DEBUGF(("first = %s, w32_short = %s\n",first, (w32_short == NULL) ? "NULL" : w32_short));
		}
		if (w32_short != NULL) {
		    y = strlen(w32_short);
		    w32_short = realloc(w32_short,y+1+x);
		    /* spool back */
		    while ( x > 0) {
			w32_short[y++] = rest[--x];
		    }
		    w32_short[y] = '\0';
		} else {
		    w32_short = malloc(strlen(win32)+1);
		    strcpy(w32_short,win32); /* last resort */
		}
		free(first);
		free(rest);
	    }   
	    ret = w32_short;
	    while (*ret) {
		if (*ret == '\\') {
		    *ret = '/';
		}
		++ret;
	    }
	    ret = w32_short;
	}    
	free(win32);
    }
    return ret;
}



char *save = "";

void save_args(int argc, char **argv) 
{
    int i;
    for(i = 0; i < argc; ++i) {
	save = add_to(save,argv[i]);
	save = add_to(save," ");
    }
}

char *progname="ld_wrap";

int my_create_pipe(HANDLE *read_p, HANDLE *write_p)
{
    char name_buff[1000];
    SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE};
    static int counter = 0;

    ++counter;

    sprintf(name_buff,"\\\\.\\pipe\\%s_%d_%d",progname,getpid(),counter);
    sa.bInheritHandle = FALSE;
    if ((*read_p = CreateNamedPipe(name_buff,
				   PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
				   PIPE_TYPE_BYTE | PIPE_READMODE_BYTE,
				   1,
				   0,
				   0,
				   2000,
				   &sa)) == INVALID_HANDLE_VALUE || 
	*read_p == NULL) {
	return 0;
    }
    sa.bInheritHandle = TRUE;
    if ((*write_p = CreateFile(name_buff,
			       GENERIC_WRITE,
			       0, /* No sharing */
			       &sa,
			       OPEN_EXISTING,
			       FILE_ATTRIBUTE_NORMAL,
			       NULL)) == INVALID_HANDLE_VALUE || 
	*write_p == NULL) {
	CloseHandle(*read_p);
	return 0;
    }
    return 1;
}

void forwardenv(void)
{
  char *(envs[]) = {"LIB","INCLUDE","LIBPATH", NULL};
  char **p = envs;
  while (*p != NULL) {
    char *val = getenv(*p);
    if (val != NULL) {
      SetEnvironmentVariable(*p,val);
    }
    ++p;
  }
}

HANDLE do_run(char *commandline, HANDLE *out, HANDLE *err) 
{
    STARTUPINFO start;
    HANDLE write_pipe_stdout = NULL, read_pipe_stdout = NULL;
    HANDLE write_pipe_stderr = NULL, read_pipe_stderr = NULL;
    SECURITY_ATTRIBUTES pipe_security;
    SECURITY_ATTRIBUTES attr;
    PROCESS_INFORMATION info;


    memset(&start,0,sizeof(start));
    memset(&pipe_security,0,sizeof(pipe_security));
    memset(&attr,0,sizeof(attr));
    memset(&info,0,sizeof(info));


    pipe_security.nLength = sizeof(pipe_security);
    pipe_security.lpSecurityDescriptor = NULL;
    pipe_security.bInheritHandle = TRUE;

    if(!my_create_pipe(&read_pipe_stdout,&write_pipe_stdout)){
	error("Could not create stdout pipes!");
    }
    if(!my_create_pipe(&read_pipe_stderr,&write_pipe_stderr)){
	error("Could not create stderr pipes!");
    }
    start.cb = sizeof (start);
    start.dwFlags = STARTF_USESHOWWINDOW;
    start.wShowWindow = SW_HIDE;
    start.hStdOutput = write_pipe_stdout;
    start.hStdError = write_pipe_stderr;
    start.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
    start.dwFlags |= STARTF_USESTDHANDLES;

    attr.nLength = sizeof(attr);
    attr.lpSecurityDescriptor = NULL;
    attr.bInheritHandle = TRUE;
    forwardenv(); /* Cygwin and windows environment variables... sigh... */
    if(!CreateProcess(NULL,
		      commandline,
		      &attr,
		      NULL,
		      TRUE,
		      CREATE_DEFAULT_ERROR_MODE,
		      NULL,
		      NULL,
		      &start,
		      &info)){
	error("Could not create process");
    }
    *out = read_pipe_stdout;
    *err = read_pipe_stderr;
    CloseHandle(write_pipe_stdout);
    CloseHandle(write_pipe_stderr);
    return info.hProcess;
}
#define HANDLE_STDOUT 0
#define HANDLE_STDERR 1
#define HANDLE_PROC 2

#ifdef HARDDEBUG
char *prefix = "";
#endif

int handle_overlapped(HANDLE fd, OVERLAPPED *ovp, char *buffer, 
		      int bufflen, int get_old, FILE *whereto, int *skip)
{
    DWORD res,read,err;
    char *ptr;

    DEBUGF(("In handle_overlapped(%d,0x%08x,0x%08x,%d,%d), prefix = %s\n",
	    fd,ovp,buffer,bufflen,get_old,prefix));
    /* hämta resultat av gamla först */
    if (get_old) {
	res = GetOverlappedResult(fd,ovp,&read,TRUE);
	DEBUGF(("read = %d, res = %d, GetLastError() = %d\n",read,res,GetLastError()));
	if (!res) {
	    return 0;
	}
	buffer[read] = '\0';
	ptr = buffer;
	while(*skip && *ptr != '\0') {
	    if (*ptr == '\n') {
		--(*skip);
	    }
	    ++ptr;
	}
	if(*ptr != '\0') {
	    fprintf(whereto,"%s",ptr);
	}
    }

    ResetEvent(ovp->hEvent);

    for(;;) {
	res = ReadFile(fd,buffer,bufflen-1,&read,ovp);

	if (!res) {
	    err = GetLastError();
	    if (err == ERROR_IO_PENDING) {
		DEBUGF(("Error I/O Pending\n"));
		return 1;
	    } 
	    DEBUGF(("ReadFileFailed for %s, %d\n",prefix,err));
	    return 0;
	}
	buffer[read] = '\0';
	ptr = buffer;
	while(*skip && *ptr != '\0') {
	    if (*ptr == '\n') {
		--(*skip);
	    }
	    ++ptr;
	}
	if(*ptr != '\0') {
	    fprintf(whereto,"%s",ptr);
	}
    }
}


int run(char *commandline,int skipout,int skiperr)
{
    HANDLE harr[3];
    HANDLE real_stdout,real_stderr;
    OVERLAPPED ov_out,ov_err;
    char outbuff[1024],errbuff[1024];
    DWORD ret,exitcode;
    HANDLE wait[3];
    int map[3];
    DWORD nwait = 3;
    int i,j;
    unsigned living_handles = 0x7;

    harr[HANDLE_STDOUT] = CreateEvent(NULL,
				      TRUE,
				      FALSE, /*not signalled */
				      NULL);  
    harr[HANDLE_STDERR] = CreateEvent(NULL,
				      TRUE,
				      FALSE,/*not signalled */
				      NULL);  

    memset(&ov_out,0,sizeof(ov_out));
    memset(&ov_err,0,sizeof(ov_err));

    ov_out.hEvent = harr[HANDLE_STDOUT];
    ov_err.hEvent = harr[HANDLE_STDERR];

    harr[HANDLE_PROC] = do_run(commandline,&real_stdout,&real_stderr);

#ifdef HARDDEBUG
    prefix = "STDOUT";
#endif
    handle_overlapped(real_stdout,&ov_out,outbuff,1024,0,stdout,&skipout);
#ifdef HARDDEBUG
    prefix = "STDERR";
#endif
    handle_overlapped(real_stderr,&ov_err,errbuff,1024,0,stderr,&skiperr);

    for(;;) {
	nwait = 0;
        for(i=0;i<3;++i) {
	    if ((living_handles & (1U << i))) {
		map[nwait] = i;
		wait[nwait++] = harr[i];
	    }
	}

	ret = WaitForMultipleObjects(nwait,
				     wait,
				     FALSE,
				     INFINITE);
	DEBUGF(("Wait returned %d\n",ret));

	if (ret == WAIT_FAILED) {
	    error("Wait failed");
	}

	ret -= WAIT_OBJECT_0;
	
	switch (map[ret]) {
	case HANDLE_PROC:
	    
	    DEBUGF(("Process died!\n"));
	    GetExitCodeProcess(harr[HANDLE_PROC],&exitcode);
	    if ((living_handles &= (~(1U<<HANDLE_PROC))) == 0) {
		goto done;
	    }
	    --nwait;
	    break;
	case HANDLE_STDOUT:
#ifdef HARDDEBUG
	    prefix = "STDOUT";
#endif
	    if (!handle_overlapped(real_stdout,&ov_out, outbuff,1024,1,stdout,&skipout)) {
		if ((living_handles &= (~(1U<<HANDLE_STDOUT))) == 0) {
		    goto done;
		}
	    }
	    break;
	case HANDLE_STDERR:
#ifdef HARDDEBUG
	    prefix = "STDERR";
#endif
	    if (!handle_overlapped(real_stderr,&ov_err, errbuff,1024,1,stderr,&skiperr)){
		if ((living_handles &= (~(1U<<HANDLE_STDERR))) == 0) {
		    goto done;
		}
	    }
	    break;
	default:
	    error("Unexpected wait result");
	}
    }
 done:
    CloseHandle(harr[HANDLE_PROC]);
    CloseHandle(harr[HANDLE_STDOUT]);
    CloseHandle(harr[HANDLE_STDERR]);
    CloseHandle(real_stdout);
    CloseHandle(real_stderr);
    return (int) exitcode;
}

int main(int argc, char **argv)
{
    int i;
    int x;
    char *s;
    char *mpath;
    char *debuglog;
    char *remove;
    FILE *debugfile;
    FILE *tmpfile;
    int filefound;
    int retval = 0;

    char *kernel_libs="kernel32.lib advapi32.lib";
    char *gdi_libs="gdi32.lib user32.lib comctl32.lib comdlg32.lib shell32.lib";
    char *default_libraries = "";
    char *cmd = "";
    char *stdlib = "MSVCRT.LIB";
    int debug_build = 0;
    int stdlib_forced = 0;
    int build_dll = 0;
    char *output_filename = "";
    char *linkadd_pdb = "";
    char *linktype = "";
    char *manifest = "";
    char *outputres = "";

    save_args(argc,argv);
    //fprintf(stderr,"ld_wrap!\n");

    default_libraries = add_to(default_libraries,kernel_libs);
    default_libraries = add_to(default_libraries," ");
    default_libraries = add_to(default_libraries,gdi_libs);
    
    for(i = 1; i < argc; ++i) {
	if (argv[i][0] == '-') {
	    char *opt = argv[i]+1;
	    switch(*opt) {
	    case 'D':
		if(strcmp(opt,"DLL")) {
		    goto filename;
		}
		build_dll = 1;
		break;
	    case 'd':
		if(!strncmp(opt,"def:",4)) {
		    mpath = do_cyp(opt+4);
		    cmd = add_to(cmd," -def:\"");
		    cmd = add_to(cmd,mpath);
		    cmd = add_to(cmd,"\"");
		    free(mpath);
		} else if(strcmp(opt,"dll")) {
		    goto filename;
		} else {
		    build_dll = 1;
		}
		break;
	    case 'L':
		mpath = do_cyp(opt+1);
		cmd = add_to(cmd," -libpath:\"");
		cmd = add_to(cmd,mpath);
		cmd = add_to(cmd,"\"");
		free(mpath);
		break;
	    case 'l':
		if(!strcmp(opt,"lMSVCRT") || !strcmp(opt,"lmsvcrt")) {
		    stdlib = "MSVCRT.LIB";
		    stdlib_forced = 1;
		} else if(!strcmp(opt,"lMSVCRTD") || !strcmp(opt,"lmsvcrtd")) {
		    stdlib = "MSVCRTD.LIB";
		    stdlib_forced = 1;
		} else if(!strcmp(opt,"lLIBCMT") || !strcmp(opt,"llibcmt")) {
		    stdlib = "LIBCMT.LIB";
		    stdlib_forced = 1;
		} else if(!strcmp(opt,"lLIBCMTD") || !strcmp(opt,"llibcmtd")) {
		    stdlib = "LIBCMTD.LIB";
		    stdlib_forced = 1;
		} else if(!strcmp(opt,"lsocket")) {
		    default_libraries = add_to(default_libraries," ");
		    default_libraries = add_to(default_libraries,"WS2_32.LIB");
		} else {
		    mpath = do_cyp(opt+1);
		    cmd = add_to(cmd," \"");
		    cmd = add_to(cmd,mpath);
		    cmd = add_to(cmd,"\"");
		    free(mpath);
		}
		break;
	    case 'g':
		debug_build = 1;
		break;
	    case 'p':
		if (strcmp(opt,"pdb:none")) {
		    goto filename;
		}
		break;
	    case 'i':
		if (!strncmp(opt,"implib:",7)) {
		    mpath = do_cyp(opt+7);
		    cmd = add_to(cmd," -implib:\"");
		    cmd = add_to(cmd,mpath);
		    cmd = add_to(cmd,"\"");
		    free(mpath);
		} else if (strcmp(opt,"incremental:no")) {
		    goto filename;
		}
		break;
	    case 'o':
		if (!strcmp(opt,"o")) {
		    ++i;
		    if (i >= argc) {
			error("-o without filename");
		    }
		    output_filename = do_cyp(argv[i]);
		} else {
		    output_filename = do_cyp(opt+1);
		}
		break;
	    default:
		goto filename;
	    }
	    continue;
	}
    filename:
	s = argv[i];
	if (*s == '/') {
	    mpath = do_cyp(s);
	    cmd = add_to(cmd," \"");
	    cmd = add_to(cmd,mpath);
	    cmd = add_to(cmd,"\"");
	    free(mpath);
	} else {
	    cmd = add_to(cmd," \"");
	    cmd = add_to(cmd,s);
	    cmd = add_to(cmd,"\"");
	}
    }
    if ((debuglog = getenv("LD_SH_DEBUG_LOG")) != NULL) {
	debugfile = fopen(debuglog,"wb+");
	if (debugfile) {
	    fprintf(debugfile,"----------------\n");
	}
    } else {
	debugfile = NULL;
    }

    if (debug_build) {
	if (!stdlib_forced) {
	    stdlib = "MSVCRTD.LIB";
	}
    }

    s = add_to("",output_filename);
    x = strlen(s);
    
    if (x >= 4 && (!strcmp(s+x-4,".exe") || !strcmp(s+x-4,".EXE") ||
		   !strcmp(s+x-4,".dll") || !strcmp(s+x-4,".DLL"))) {
	*(s+x-3) = '\0';
	linkadd_pdb = add_to(linkadd_pdb,"-pdb:\"");
	linkadd_pdb = add_to(linkadd_pdb,s);
	linkadd_pdb = add_to(linkadd_pdb,"pdb\"");
    } else if (!x) {
	linkadd_pdb = add_to(linkadd_pdb,"-pdb:\"a.pdb\"");
    } else {
	linkadd_pdb = add_to(linkadd_pdb,"-pdb:\"");
	linkadd_pdb = add_to(linkadd_pdb,s);
	linkadd_pdb = add_to(linkadd_pdb,".pdb\"");
    }
    free(s);

    
    linktype = add_to(linktype,"-debug ");
    linktype = add_to(linktype,linkadd_pdb);

    free(linkadd_pdb);

    s = add_to("",output_filename);
    x = strlen(s);

    if (build_dll) {
	if (x >= 4 && (!strcmp(s+x-4,".exe") || !strcmp(s+x-4,".EXE") ||
		       !strcmp(s+x-4,".dll") || !strcmp(s+x-4,".DLL"))) {
	    
	    if (!strcmp(s+x-4,".exe") || !strcmp(s+x-4,".EXE")) {
		fprintf(stderr,"Warning, output set to .exe when building DLL");
	    }
	    mpath = cmd;
	    cmd = add_to("","-dll -out:\"");
	    cmd = add_to(cmd,s);
	    cmd = add_to(cmd,"\" ");
	    cmd = add_to(cmd,mpath);
	    if (*mpath) {
		free(mpath);
	    }

	    outputres = add_to(outputres,output_filename);
	    outputres = add_to(outputres,";2");
	    manifest = add_to(manifest,output_filename);
	    manifest = add_to(manifest,".manifest");
	} else if (x == 0) {
	    mpath = cmd;
	    cmd = add_to("","-dll -out:\"a.dll\" ");
	    cmd = add_to(cmd,mpath);
	    if (*mpath) {
		free(mpath);
	    }

	    outputres = add_to(outputres,"a.dll;2");
	    manifest = add_to(manifest,"a.dll.manifest");
	} else {
	    mpath = cmd;
	    cmd = add_to("","-dll -out:\"");
	    cmd = add_to(cmd,s);
	    cmd = add_to(cmd,".dll\" ");
	    cmd = add_to(cmd,mpath);
	    if (*mpath) {
		free(mpath);
	    }

	    outputres = add_to(outputres,output_filename);
	    outputres = add_to(outputres,".dll;2");
	    manifest = add_to(manifest,output_filename);
	    manifest = add_to(manifest,".dll.manifest");
	}
    } else {
	if (x >= 4 && (!strcmp(s+x-4,".exe") || !strcmp(s+x-4,".EXE") ||
		       !strcmp(s+x-4,".dll") || !strcmp(s+x-4,".DLL"))) {
	    
	    if (!strcmp(s+x-4,".dll") || !strcmp(s+x-4,".DLL")) {
		fprintf(stderr,"Warning, output set to .exe when building DLL");
	    }
	    mpath = cmd;
	    cmd = add_to("","-out:\"");
	    cmd = add_to(cmd,s);
	    cmd = add_to(cmd,"\" ");
	    cmd = add_to(cmd,mpath);
	    if (*mpath) {
		free(mpath);
	    }

	    outputres = add_to(outputres,output_filename);
	    outputres = add_to(outputres,";1");
	    manifest = add_to(manifest,output_filename);
	    manifest = add_to(manifest,".manifest");
	} else if (x == 0) {
	    mpath = cmd;
	    cmd = add_to("","-out:\"a.exe\" ");
	    cmd = add_to(cmd,mpath);
	    if (*mpath) {
		free(mpath);
	    }

	    outputres = add_to(outputres,"a.exe;1");
	    manifest = add_to(manifest,"a.exe.manifest");
	} else {
	    mpath = cmd;
	    cmd = add_to("","-out:\"");
	    cmd = add_to(cmd,s);
	    cmd = add_to(cmd,".exe\" ");
	    cmd = add_to(cmd,mpath);
	    if (*mpath) {
		free(mpath);
	    }

	    outputres = add_to(outputres,output_filename);
	    outputres = add_to(outputres,".exe;1");
	    manifest = add_to(manifest,output_filename);
	    manifest = add_to(manifest,".exe.manifest");
	}
    }
	    
    s = cmd;
    cmd = add_to("","link.exe ");
    cmd = add_to(cmd,linktype);
    cmd = add_to(cmd," -nologo -incremental:no ");
    cmd = add_to(cmd,s);
    cmd = add_to(cmd," ");
    cmd = add_to(cmd,stdlib);
    cmd = add_to(cmd," ");
    cmd = add_to(cmd,default_libraries);

    if (*s) {
	free(s);
    }
	

    if (debugfile) {
	fprintf(debugfile,"%s\n",save);
	fprintf(debugfile,"%s\n",cmd);
    }
    
    retval = run(cmd,0,0);
    
    
    mpath = do_cyp(manifest);
    filefound = 0;
    tmpfile = fopen(mpath,"rb");
    if (tmpfile != NULL) {
	filefound = 1;
	fclose(tmpfile);
    }
    if (retval == 0 && filefound) {
	s = add_to("","mt.exe -nologo -manifest \"");
	s = add_to(s,manifest);
	s = add_to(s,"\" -outputresource:\"");
	s = add_to(s,outputres);
	s = add_to(s,"\"");
	if (debugfile) {
	    fprintf(debugfile,"%s\n",s);
	}
	retval = run(s,0,0);
	if (*s) {
	    free(s);
	}
	if (retval) {
	    /* cleanup needed */
	    remove = add_to("",outputres);
	    x = strlen(remove);
	    remove[x-2] = '\0';
	    if (debugfile) {
		fprintf(debugfile,"remove %s\n",remove);
	    }
	    DeleteFile(remove);
	    free(remove);
	}
	if (debugfile) {
	    fprintf(debugfile,"remove %s\n",manifest);
	}
	DeleteFile(manifest);
    }
    return retval;
}
	    
	    
		

			
		    
		    
		
	    
		    








