// **********************************************************************
//
// Copyright (c) 1999
// Object Oriented Concepts, Inc.
// Billerica, MA, USA
//
// All Rights Reserved
//
// **********************************************************************

#include <OB/CORBA.h>
#include <OB/Util.h>

#ifdef HAVE_FSTREAM
#   include <fstream>
#else
#   include <fstream.h>
#endif

#include <Types.h>
#include <GenUtil.h>
#include <GenCPP.h>
#include <IntRep_impl.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>

#ifdef WIN32
#   define popen _popen
#   define pclose _pclose
#endif

static void
usage(const char* progName)
{
    cerr << "Usage:\n";
    cerr << progName << " [options] idl-files ...\n"
      "\n"
      "Options:\n"
      "-h, --help               Show this message.\n"
      "-v, --version            Show ORBacus version.\n"
      "-e, --cpp NAME           Use NAME as preprocessor.\n"
      "-d, --debug              Print diagnostic messages.\n"
      "-DNAME                   Define NAME as 1.\n"
      "-DNAME=DEF               Define NAME as DEF.\n"
      "-UNAME                   Remove any definition for NAME.\n"
      "-IDIR                    Put DIR in the include file search path.\n"
      "--no-skeletons           Don't generate skeletons classes.\n"
      "--no-type-codes          Don't generate type codes and insertion and\n"
      "                         extraction functions for the any type.\n"
      "--locality-constrained   Create locality-constrained objects.\n"
      "--no-virtual-inheritance Don't use virtual inheritance.\n"
      "--tie                    Generate tie classes.\n"
      "--c-suffix SUFFIX        Use SUFFIX as suffix for source files.\n"
      "                         The default value is `.cpp'.\n"
      "--h-suffix SUFFIX        Use SUFFIX as suffix for header files.\n"
      "                         The default value is `.h'.\n"
      "--all                    Generate code for included files instead\n"
      "                         of inserting #include statements.\n"
      "--no-relative            Don't make #include statements for included\n"
      "                         files relative to the paths specified with\n"
      "                         the -I options.\n"
      "--header-dir DIR         Make #include statements for header files\n"
      "                         relative to DIR.\n"
      "--other-header-dir DIR   Make #include statements for header files\n"
      "                         for included IDL files relative to DIR.\n"
      "--output-dir DIR         Write generated files to directory DIR.\n"
      "--file-list FILE         Write a list of generated files to FILE.\n"
      "--dll-import DEF         Put DEF in front of every symbol that needs\n"
      "                         an explicit DLL import statement.\n"
	;
}

int
run(int argc, char* argv[])
{
    //
    // Create ORB
    //
    CORBA_ORB_var orb = CORBA_ORB_init(argc, argv);

    //
    // Set program name
    //
    const char* progName = argv[0];

    //
    // Get the C preprocessor
    //
    CORBA_String_var cppCmd = CORBA_string_dup(progName);
#ifdef WIN32
    char* p1 = strrchr(cppCmd.inout(), '\\');
    char* p2 = strrchr(cppCmd.inout(), '/');
    char* p = p1 > p2 ? p1 : p2;
#else
    char* p = strrchr(cppCmd.inout(), '/');
#endif
    if(p)
    {
	p++;
	*p = '\0';
	cppCmd += "idlcpp";
    }
    else
	cppCmd = CORBA_string_dup("idlcpp");

    //
    // Get options
    //
    CORBA_String_var cppArgs;
    bool partial = true;
    bool relative = true;
    bool noTC = false;
    bool local = false;
    bool noVirtual = false;
    bool noSkel = false;
    bool tie = false;
    CORBA_String_var cSuff = CORBA_string_dup(".cpp");
    CORBA_String_var hSuff = CORBA_string_dup(".h");
    CORBA_String_var headerDir = CORBA_string_dup("");
    CORBA_String_var otherHeaderDir = CORBA_string_dup("");
    CORBA_String_var outDir = CORBA_string_dup("");
    CORBA_String_var fileList = CORBA_string_dup("");
    CORBA_String_var dllImport = CORBA_string_dup("");
    IdlStringSeq includePathSeq;

    CORBA_Long i;
    for(i = 1 ; i < argc && *argv[i] == '-' ; i++)
    {
	if(strcmp(argv[i], "--help") == 0 ||
	   strcmp(argv[i], "-h") == 0)
	{
	    usage(progName);
	    return 0;
	}
	else if(strcmp(argv[i], "--version") == 0 ||
		strcmp(argv[i], "-v") == 0)
	{
	    cerr << "ORBacus " << OBVersion << endl;
	    return 0;
	}
	else if(strcmp(argv[i], "--cpp") == 0 ||
		strcmp(argv[i], "-e") == 0)
	{
	    if(++i < argc)
		cppCmd = CORBA_string_dup(argv[i]);
	    else
	    {
		cerr << progName << ": argument expected for `"
		     << argv[i - 1] << "'" << endl;
		return 1;
	    }
	}
	else if(strcmp(argv[i], "--debug") == 0 ||
		strcmp(argv[i], "-d") == 0)
	{
	    extern int yydebug;
	    yydebug = 1;
	}
	else if(strncmp(argv[i], "-D", 2) == 0 ||
		strncmp(argv[i], "-U", 2) == 0)
	{
	    if(strlen(argv[i]) == 2)
	    {
		cerr << progName << ": argument expected for `"
		     << argv[i] << "'" << endl; // not argv[i - 1] !
		return 1;
	    }

	    cppArgs += ' ';
	    cppArgs += argv[i];
	}
	else if(strncmp(argv[i], "-I", 2) == 0)
	{
	    if(strlen(argv[i]) == 2)
	    {
		cerr << progName << ": argument expected for `"
		     << argv[i] << "'" << endl; // not argv[i - 1] !
		return 1;
	    }

	    cppArgs += ' ';
	    cppArgs += argv[i];

	    const char* path = argv[i] + 2;
	    if(strlen(path))
	    {
		CORBA_String_var newPath =
		    CORBA_string_alloc(strlen(path) + 1);
		strcpy(newPath.inout(), path);
#ifdef WIN32
		strcat(newPath.inout(), "\\");
#else
		strcat(newPath.inout(), "/");
#endif
		includePathSeq.append(newPath);
	    }
	}
	else if(strcmp(argv[i], "--no-type-codes") == 0)
	{
	    noTC = true;
	}
	else if(strcmp(argv[i], "--locality-constrained") == 0)
	{
	    local = true;
	}
	else if(strcmp(argv[i], "--no-virtual-inheritance") == 0)
	{
	    noVirtual = true;
	}
	else if(strcmp(argv[i], "--all") == 0)
	{
	    partial = false;
	}
	else if(strcmp(argv[i], "--no-skeletons") == 0)
	{
	    noSkel = true;
	}
	else if(strcmp(argv[i], "--tie") == 0)
	{
	    tie = true;
	}
	else if(strcmp(argv[i], "--c-suffix") == 0)
	{
	    if(++i < argc)
		cSuff = CORBA_string_dup(argv[i]);
	    else
	    {
		cerr << progName << ": argument expected for `"
		     << argv[i - 1] << "'" << endl;
		return 1;
	    }
	}
	else if(strcmp(argv[i], "--h-suffix") == 0)
	{
	    if(++i < argc)
		hSuff = CORBA_string_dup(argv[i]);
	    else
	    {
		cerr << progName << ": argument expected for `"
		     << argv[i - 1] << "'" << endl;
		return 1;
	    }
	}
	else if(strcmp(argv[i], "--no-relative") == 0)
	{
	    relative = false;
	}
	else if(strcmp(argv[i], "--header-dir") == 0)
	{
	    if(++i < argc)
		headerDir = CORBA_string_dup(argv[i]);
	    else
	    {
		cerr << progName << ": argument expected for `"
		     << argv[i - 1] << "'" << endl;
		return 1;
	    }
	}
	else if(strcmp(argv[i], "--other-header-dir") == 0)
	{
	    if(++i < argc)
		otherHeaderDir = CORBA_string_dup(argv[i]);
	    else
	    {
		cerr << progName << ": argument expected for `"
		     << argv[i - 1] << "'" << endl;
		return 1;
	    }
	}
	else if(strcmp(argv[i], "--output-dir") == 0)
	{
	    if(++i < argc)
		outDir = CORBA_string_dup(argv[i]);
	    else
	    {
		cerr << progName << ": argument expected for `"
		     << argv[i - 1] << "'" << endl;
		return 1;
	    }
	}
	else if(strcmp(argv[i], "--file-list") == 0)
	{
	    if(++i < argc)
		fileList = CORBA_string_dup(argv[i]);
	    else
	    {
		cerr << progName << ": argument expected for `"
		     << argv[i - 1] << "'" << endl;
		return 1;
	    }
	}
	else if(strcmp(argv[i], "--dll-import") == 0)
	{
	    if(++i < argc)
		dllImport = CORBA_string_dup(argv[i]);
	    else
	    {
		cerr << progName << ": argument expected for `"
		     << argv[i - 1] << "'" << endl;
		return 1;
	    }
	}
	else
	{
	    cerr << progName << ": unknown option `"
		 << argv[i] << "'\n" << endl;
	    usage(progName);
	    return 1;
	}
    }

    if(i == argc)
    {
	cerr << progName << ": no input file" << endl;
	usage(progName);
	return 1;
    }

    //
    // Open file for generated file list
    //
    ofstream out;
    if(strlen(fileList))
    {
	out.open(fileList);
	if(!out.good())
	{
	    cerr << progName << ": can't open \"" << fileList
		 << "\": " << strerror(errno) << endl;
	    return 1;
	}
    }

    int status = 0;
    while(status == 0 && i < argc)
    {
	CORBA_String_var file = CORBA_string_dup(argv[i++]);
	
	//
	// Remove .idl or .IDL from idl file name
	//
	CORBA_String_var base = file;
	const char* suffix = strrchr(base, '.');
	if(suffix)
	{
	    if(strcmp(suffix, ".idl") == 0 || strcmp(suffix, ".IDL") == 0)
	    {
		char* newBase = CORBA_string_alloc(suffix - (const char*)base);
		strncpy(newBase, base, suffix - (const char*)base);
		newBase[suffix - (const char*)base] = '\0';
		base = newBase;
	    }
	}
	
	//
	// Check for input file
	//
	struct stat statBuf;
	if(stat(file, &statBuf) == -1)
	{
	    if(strlen(fileList))
		out.close();

	    cerr << progName << ": couldn't open " << file << endl;
	    return 1;
	}

	//
	// Check for C++ proprocessor
	//
	if(!IdlCheckForCPP(progName, cppCmd))
	{
	    if(strlen(fileList))
		out.close();

	    return 1;
	}

	//
	// Open input file
	//
	extern FILE* yyin;
	CORBA_String_var cmd = cppCmd;
	cmd += cppArgs;
	cmd += ' ';
	cmd += file;

	if((yyin = popen(cmd, "r")) == NULL)
	{
	    if(strlen(fileList))
		out.close();

	    return 1;
	}

	//
	// Create implementation object
	//
	CORBA_Repository_impl* repository = new CORBA_Repository_impl(orb);
	
	//
	// Do parsing
	//
	int IdlParse(CORBA_ORB_ptr, CORBA_Repository_ptr,
		     CORBA_RepositoryIdSeq*&, IdlStringSeq*&, IdlCommentSeq*&);
	CORBA_RepositoryIdSeq_var idSeq; // This is an out parameter
	IdlStringSeq_var includeSeq; // This is an out paramter
	IdlCommentSeq_var commentSeq; // This is an out paramter
	status = IdlParse(orb, repository,
			  idSeq.out(), includeSeq.out(), commentSeq.out());
	
	//
	// Close input file
	//
	pclose(yyin);
	
	if(status == 0)
	{
	    for(CORBA_ULong k = 0 ; k < includeSeq -> length() ; k++)
	    {
		//
		// Make include file names relative to the paths specified
		// with the -I options
		//
		if(relative)
		{
		    for(CORBA_ULong l = 0 ; l < includePathSeq.length() ; l++)
		    {
#ifdef WIN32
			CORBA_ULong m;

			CORBA_String_var s1 = includeSeq[k];
			for(m = 0 ; m < strlen(s1) ; m++)
			    if(s1[m] == '\\')
				s1[m] = '/';

			CORBA_String_var s2 = includePathSeq[l];
			for(m = 0 ; m < strlen(s2) ; m++)
			    if(s2[m] == '\\')
				s2[m] = '/';

			if(strncmp(s1, s2, strlen(s2)) == 0)
#else
			if(strncmp(includeSeq[k], includePathSeq[l],
				   strlen(includePathSeq[l])) == 0)
#endif
			{
			    includeSeq[k] =
				CORBA_string_dup((const char*)includeSeq[k] +
						 strlen(includePathSeq[l]));
			}
		    }
		}
		
		//
		// Remove .idl or .IDL from include file names
		//
		const char* inc = includeSeq[k];
		const char* suffix = strrchr(inc, '.');
		if(suffix)
		{
		    if(strcmp(suffix, ".idl") == 0 ||
		       strcmp(suffix, ".IDL") == 0)
		    {
			char* newInc = CORBA_string_alloc(suffix - inc);
			strncpy(newInc, inc, suffix - inc);
			newInc[suffix - inc] = '\0';
			includeSeq[k] = newInc;
		    }
		}
	    }
	    
	    //
	    // Generate C++ mapping
	    //
#ifdef WIN32
	    const char* name1 = strrchr(base, '\\');
	    const char* name2 = strrchr(base, '/');
	    const char* name = name1 > name2 ? name1 : name2;
#else
	    const char* name = strrchr(base, '/');
#endif
	    if(name)
		name++;
	    else
		name = base;
	    
	    if(strlen(outDir))
	    {
#ifdef WIN32
		outDir += '\\';
#else
		outDir += '/';
#endif
	    }

	    IdlStringSeq_var fileNameSeq = new IdlStringSeq;

	    if(partial)
	    {
		IdlCPPGenerator generator(progName, repository,
					  idSeq, includeSeq, fileNameSeq, noTC,
					  local, noVirtual,
					  headerDir, otherHeaderDir,
					  dllImport, hSuff, cSuff);
		
		status = generator.gen(outDir, name);
		
		if(status == 0 && !noSkel)
		    status = generator.genSkel(outDir, name);

		if(status == 0 && tie)
		    generator.genTie(outDir, name);
	    }
	    else
	    {
		IdlCPPGenerator generator(progName, repository, fileNameSeq,
					  noTC, local, noVirtual,
					  headerDir, otherHeaderDir,
					  dllImport, hSuff, cSuff);
		
		status = generator.gen(outDir, name);
		
		if(status == 0 && !noSkel)
		    status = generator.genSkel(outDir, name);

		if(status == 0 && tie)
		    generator.genTie(outDir, name);
	    }

	    if(status == 0 && strlen(fileList))
	    {
		for(CORBA_ULong i = 0 ; i < fileNameSeq -> length() ; i++)
		    out << fileNameSeq[i] << endl;
	    }
	}
	
	//
	// Release internal object references to resolve
	// cyclic dependencies
	//
	repository -> releaseInternal();
	
	//
	// Release repository
	//
	CORBA_release(repository);
    }

    if(strlen(fileList))
	out.close();

    return status;
}

int
main(int argc, char* argv[], char*[])
{
    int status;

    try
    {
	//
	// Generate code
	//
	status = run(argc, argv);
    }
    catch(CORBA_SystemException& ex)
    {
	OBPrintException(ex);
	status = 1;
    }

    return status;
}
