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

#include <OB/CORBA.h>
#include <Types.h>
#include <GramUtil.h>

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>

extern long IdlLineno;
extern IdlStringSeq IdlFileStack;
extern CORBA_String_var IdlDocComment;

extern CORBA_Repository_ptr IdlRepository;

extern CORBA_RepositoryIdSeq* IdlTopFileContent;
extern IdlStringSeq* IdlIncludes;

extern IdlCommentSeq* IdlComments;

extern IdlContainerSeq IdlContStack;
extern IdlStringSeqSeq IdlPrefStackStack;

// ----------------------------------------------------------------------
// Find scoped name
// ----------------------------------------------------------------------

CORBA_IRObject_ptr
IdlFindScopedName(CORBA_Container_ptr con, const char* scoped)
{
    CORBA_IRObject_var irObj;

    //
    // Object is a keyword. TypeCode and Principal are not.
    //
    if(strcmp(scoped, "Object") == 0)
    {
	irObj = IdlRepository -> get_primitive(CORBA_pk_objref);
    }
    else if(strcmp(scoped, "CORBA::TypeCode") == 0 ||
	    strcmp(scoped, "::CORBA::TypeCode") == 0)
    {
	irObj = IdlRepository -> get_primitive(CORBA_pk_TypeCode);
    }
    else if(strcmp(scoped, "CORBA::Principal") == 0 ||
	    strcmp(scoped, "::CORBA::Principal") == 0)
    {
	irObj = IdlRepository -> get_primitive(CORBA_pk_Principal);
    }
    else
    {
	if(strcmp(scoped, "TypeCode") == 0 || strcmp(scoped, "Principal") == 0)
	{
	    CORBA_Contained_var contained = CORBA_Contained::_narrow(con);
	    if(!CORBA_is_nil(contained))
	    {
		CORBA_ScopedName_var contScoped = contained -> absolute_name();
		if(strcmp(contScoped, "::CORBA") == 0 ||
		   strncmp(contScoped, "::CORBA::", 7) == 0)
		{
		    if(strcmp(scoped, "TypeCode") == 0)
		    {
			irObj =
			    IdlRepository -> get_primitive(CORBA_pk_TypeCode);
		    }
		    else if(strcmp(scoped, "Principal") == 0)
		    {
			irObj =
			    IdlRepository -> get_primitive(CORBA_pk_Principal);
		    }
		    else
			assert(false);
		}
	    }
	}

	if(CORBA_is_nil(irObj))
	{
	    CORBA_Container_var container = CORBA_Container::_duplicate(con);
	    while(CORBA_is_nil(irObj))
	    {
		try
		{
		    irObj = container -> lookup(scoped);
		}
		catch(const CORBA_INTF_REPOS& ex)
		{
		    IdlPrintException(ex.minor(), "", scoped);
		    return CORBA_IRObject::_nil();
		}
		
		if(CORBA_is_nil(irObj))
		{
		    CORBA_Contained_var contained =
			CORBA_Contained::_narrow(container);
		
		    if(CORBA_is_nil(contained))
			break;
		    
		    container = contained -> defined_in();
		}
	    }
	}
    }
    
    if(CORBA_is_nil(irObj))
    {	
	CORBA_String_var err = CORBA_string_dup("`");
	err += scoped;
	err += "' undeclared";
	IdlError(err);
	return CORBA_IRObject::_nil();
    }

    return irObj._retn();
}

// ----------------------------------------------------------------------
// Find an enum member
// ----------------------------------------------------------------------

CORBA_Long
IdlFindEnumMember(CORBA_Container_ptr container, CORBA_EnumDef_ptr enumDef,
		  const char* scoped)
{
    const char* n = strrchr(scoped, ':');
    CORBA_ScopedName_var s;
    
    if(n)
    {
	assert(n > scoped);
	assert(*(n - 1) == ':');
	n++;
	
	s = CORBA_string_alloc(n - scoped);
	strncpy(s.inout(), scoped, n - scoped);
	s[n - scoped] = '\0';
    }
    else
    {
	n = scoped;
	s = CORBA_string_dup("");
    }
    
    CORBA_Identifier_var enumName = enumDef -> name();
    CORBA_ScopedName_var scopedEnumName = s;
    scopedEnumName += enumName;
    
    CORBA_Long result = -1;
    CORBA_EnumMemberSeq_var memberSeq = enumDef -> members();
    for(CORBA_ULong i = 0 ; i < memberSeq -> length() ; i++)
	if(strcmp(memberSeq[i], n) == 0)
	{
	    result = (CORBA_Long)i;
	    break;
	}
    
    if(result == -1)
    {
	CORBA_String_var err = CORBA_string_dup("`");
	err += n;
	err += "' is not a member of `";
	err += enumName;
	err += "'";
	IdlError(err);
	return -1;
    }

    CORBA_EnumDef_var enumDef2;
    do
    {
	try
	{
	    CORBA_Contained_var contained =
		container -> lookup(scopedEnumName);
	    enumDef2 = CORBA_EnumDef::_narrow(contained);
	}
	catch(const CORBA_INTF_REPOS& ex)
	{
	    IdlPrintException(ex.minor(), "", scopedEnumName);
	    return -1;
	}
	
	if(CORBA_is_nil(enumDef2))
	{
	    CORBA_Contained_var contained =
		CORBA_Contained::_narrow(container);
	    
	    if(CORBA_is_nil(contained))
		break;
	    
	    container = contained -> defined_in();
	}
    }
    while(CORBA_is_nil(enumDef2));
    
    if(!CORBA_is_nil(enumDef2))
    {
	CORBA_RepositoryId_var id = enumDef -> id();
	CORBA_RepositoryId_var id2 = enumDef2 -> id();
	if(strcmp(id, id2) != 0)
	    enumDef2 = CORBA_EnumDef::_nil();
    }
    
    if(CORBA_is_nil(enumDef2))
    {	
	CORBA_String_var err =
	    CORBA_string_dup("wrong scope for enum member `");
	err += scoped;
	err += "'";
	IdlError(err);
	return -1;
    }

    return result;
}

// ----------------------------------------------------------------------
// Output errors and warnings
// ----------------------------------------------------------------------

void
IdlError(const char* s)
{
    const char* n = IdlFileStack[IdlFileStack.length() - 1];

    if(strcmp(n, "") == 0)
	n = "standard input";

    fflush(stderr);

    // Select XEmacs or VC++ IDE display format
#ifdef WIN32
    fprintf(stderr, "%s(%ld): error: %s\n", n, IdlLineno, s);
#else
    fprintf(stderr, "%s:%ld: error: %s\n", n, IdlLineno, s);
#endif
}

void
IdlWarning(const char* s)
{
    const char* n = IdlFileStack[IdlFileStack.length() - 1];

    if(strcmp(n, "") == 0)
	n = "standard input";

    fflush(stderr);

    // Select XEmacs or VC++ IDE display format
#ifdef WIN32
    fprintf(stderr, "%s(%ld): warning: %s\n", n, IdlLineno, s);
#else
    fprintf(stderr, "%s:%ld: warning: %s\n", n, IdlLineno, s);
#endif
}

// ----------------------------------------------------------------------
// Parse line number and file name
// ----------------------------------------------------------------------

void
IdlParseLineAndFile(const char* yytext)
{
    CORBA_String_var str = CORBA_string_dup(yytext + 1);
    char* tok;
    static const char* sep = " \t\n";

    tok = strtok(str.inout(), sep);

    //
    // Filter out "line"
    //
    if(tok && strcmp(tok, "line") == 0)
	tok = strtok(0, sep);

    if(tok)
    {
	//
	// Get line number information
	//
	extern long IdlLineno;
	IdlLineno = atol(tok);
	
	if((tok = strtok(0, sep)))
	{
	    //
	    // Get file name information
	    //
	    if(tok[0] == '"')
		tok++;
	    
	    if(tok[strlen(tok) - 1] == '"')
		tok[strlen(tok) - 1] = '\0';
	    
	    if(strlen(tok))
	    {
#ifdef WIN32
		CORBA_String_var newFileName = CORBA_string_alloc(strlen(tok));
		CORBA_ULong i, j;
		for(i = 0, j = 0 ; i < strlen(tok) ; i++, j++)
		{
		    if(tok[i] == '\\' && tok[i + 1] == '\\')
		    {
			i++;
			newFileName[j] = '\\';
		    }
		    else
			newFileName[j] = tok[i];
		}

		newFileName[j] = '\0';
#else
		CORBA_String_var newFileName = CORBA_string_dup(tok);
#endif

		CORBA_ULong len = IdlFileStack.length();
		assert(len > 0);
		assert(IdlPrefStackStack.length() == len);

		if(strcmp(IdlFileStack[len - 1], newFileName) != 0)
		{
		    if(len == 1)
		    {
			IdlFileStack.length(len + 1);
			IdlFileStack[len] = newFileName;
			IdlPrefStackStack.length(len + 1);
			IdlPrefStackStack[len] = IdlPrefStackStack[0];
		    }
		    else
		    {
			if(strcmp(IdlFileStack[len - 2], newFileName) == 0)
			{
			    IdlFileStack.length(len - 1);
			    IdlPrefStackStack.length(len - 1);
			}
			else
			{
			    IdlFileStack.length(len + 1);
			    IdlFileStack[len] = newFileName;
			    IdlPrefStackStack.length(len + 1);
			    IdlPrefStackStack[len] = IdlPrefStackStack[0];

			    if(len == 2)
				IdlIncludes -> append(newFileName);
			}
		    }
		}
	    }
	}
    }
}

// ----------------------------------------------------------------------
// Parse pragma
// ----------------------------------------------------------------------

bool
IdlParsePragma(const char* yytext)
{
    static const char* pragmaStr = "pragma";

    while(strncmp(yytext, pragmaStr, strlen(pragmaStr)) != 0)
    {
	yytext++;
	assert(*yytext);
    }

    CORBA_String_var str = CORBA_string_dup(yytext + strlen(pragmaStr));
    char* tok;
    static const char* sep = " \t\n";
    bool formatError = true;

    if((tok = strtok(str.inout(), sep)))
    {
	//
	// The prefix pragma
	//
	if(strcmp(tok, "prefix") == 0)
	{
	    if((tok = strtok(0, sep)))
	    {
		int len = strlen(tok);
		if(len >= 2 && tok[0] == '"' && tok[len - 1] == '"')
		{
		    if(len == 2)
			tok[len - 1] = '\0'; // For #pragma prefix ""
		    else
			tok[len - 1] = '/';

		    tok++;

 		    CORBA_ULong stackStackLen = IdlPrefStackStack.length();
 		    assert(stackStackLen > 0);
		    
 		    CORBA_ULong stackLen =
			IdlPrefStackStack[stackStackLen - 1].length();
 		    assert(stackLen > 0);
		    
 		    IdlPrefStackStack[stackStackLen - 1][stackLen - 1] =
			CORBA_string_dup(tok);

		    formatError = false;
		}
	    }
	}
	//
	// The ID pragma
	//
	else if(strcmp(tok, "ID") == 0)
	{
	    if((tok = strtok(0, sep)))
	    {
		assert(IdlContStack.length() > 0);
		CORBA_Container_ptr container =
		    IdlContStack[IdlContStack.length() - 1];
		CORBA_IRObject_var irObj = IdlFindScopedName(container, tok);
		if(CORBA_is_nil(irObj))
		    return false;

		CORBA_Contained_var contained =
		    CORBA_Contained::_narrow(irObj);
		
		if(CORBA_is_nil(contained))
		{
		    CORBA_String_var err =
			CORBA_string_dup("#pragma: can't set repository id "
					 "for `");
		    err += tok;
		    err += "'";
		    IdlError(err);
		    return false;
		}
		else if((tok = strtok(0, sep)))
		{
		    int len = strlen(tok);
		    if(len >= 2 && tok[0] == '"' && tok[len - 1] == '"')
		    {
			tok[len - 1] = '\0';
			tok++;
			
			CORBA_RepositoryId_var old = contained -> id();
			if(strcmp(tok, old) != 0)
			{
			    contained -> id(tok);
			    
			    CORBA_ULong i;
			    
			    for(i = 0 ; i < IdlComments -> length() ; i++)
			    {
				if(strcmp((*IdlComments)[i].id, old))
				{
				    (*IdlComments)[i].id =
					CORBA_string_dup(tok);
				}
			    }
			    
			    for(i = 0 ; i < IdlTopFileContent -> length() ;
				i++)
			    {
				if(strcmp((*IdlTopFileContent)[i], old) == 0)
				{
				    (*IdlTopFileContent)[i] =
					CORBA_string_dup(tok);
				}
			    }
				    
			}

			formatError = false;
		    }
		}
	    }
	}
	//
	// The version pragma
	//
	else if(strcmp(tok, "version") == 0)
	{
	    if((tok = strtok(0, sep)))
	    {
		assert(IdlContStack.length() > 0);
		CORBA_Container_ptr container =
		    IdlContStack[IdlContStack.length() - 1];
		CORBA_IRObject_var irObj = IdlFindScopedName(container, tok);
		if(CORBA_is_nil(irObj))
		    return false;

		CORBA_Contained_var contained =
		    CORBA_Contained::_narrow(irObj);
		
		if(CORBA_is_nil(contained))
		{
		    CORBA_String_var err =
			CORBA_string_dup("#pragma: can't set version for `");
		    err += tok;
		    err += "'";
		    IdlError(err);
		    return false;
		}
		else if((tok = strtok(0, sep)))
		{
		    CORBA_String_var version = CORBA_string_dup(tok);

		    char* major = strtok(tok, ".");
		    if(major)
		    {
			char* minor = strtok(0, ".");
			if(minor)
			{
			    char* dummy = strtok(0, ".");
			    if(!dummy)
			    {
				bool invalid = false;

				CORBA_ULong i;

				for(i = 0 ; i < strlen(major) ; i++)
				    if(!isdigit(major[i]))
					invalid = true;

				for(i = 0 ; i < strlen(minor) ; i++)
				    if(!isdigit(minor[i]))
					invalid = true;

				if(!invalid)
				{
				    CORBA_RepositoryId_var old =
					contained -> id();
				    
				    CORBA_RepositoryId_var id = old;
				    char* p = strrchr(id.inout(), ':');
				    if(p)
					*p = '\0';
				    
				    id += ':';
				    id += version;
				    contained -> version(version);
				    
				    if(strcmp(id, old) != 0)
				    {
					contained -> id(id);
					
					for(i = 0 ;
					    i < IdlComments -> length() ; i++)
					{
					    if(strcmp((*IdlComments)[i].id,
						      old))
					    {
						(*IdlComments)[i].id = id;
					    }
					}
					
					for(i = 0 ;
					    i < IdlTopFileContent -> length() ;
					    i++)
					{
					    if(strcmp((*IdlTopFileContent)[i],
						      old) == 0)
					    {
						(*IdlTopFileContent)[i] = id;
					    }
					}
				    }

				    formatError = false;
				}
			    }
			}
		    }
		}
	    }
	}
    }

    if(formatError)
	IdlWarning("#pragma: unknown format");

    return true;
}

// ----------------------------------------------------------------------
// Parse C-style comment (i.e. comment within /**/)
// ----------------------------------------------------------------------

void
IdlParseComment(const char* doc)
{
    assert(doc);

    CORBA_String_var str = doc;

    if(str[0] == '*')
    {
	IdlDocComment = CORBA_string_dup("");

	char* s1 = str.inout() + 1;
	char* s2;

	while(true)
	{
	    while(*s1 == '*')
		s1++;
	    
	    s2 = s1;
	    
	    while(*s2 != '\n' && *s2 != '\0')
		s2++;
	    
	    bool end = false;
	    if(*s2 == '\0')
		end = true;
	    else
		*s2 = '\0';
	    
	    IdlDocComment += s1;
	    
	    if(end)
		break;

	    IdlDocComment += '\n';

	    s1 = s2 + 1;

	    while(*s1 == ' ' || *s1 == '\t')
		s1++;
	}
    }
}

// ----------------------------------------------------------------------
// Get repository id
// ----------------------------------------------------------------------

char*
IdlGetRepositoryId(const char* name, const char* version)
{
    CORBA_ULong stackStackLen = IdlPrefStackStack.length();
    assert(stackStackLen > 0);
    
    CORBA_ULong stackLen = IdlPrefStackStack[stackStackLen - 1].length();
    assert(stackLen > 0);
    
    const char* prefix = IdlPrefStackStack[stackStackLen - 1][stackLen - 1];

    CORBA_String_var str = CORBA_string_dup("IDL:");
    str += prefix;
    str += name;
    str += ':';
    str += version;

    return str._retn();
}

// ----------------------------------------------------------------------
// Print CORBA_INTF_REPOS exception as error message
// ----------------------------------------------------------------------

void
IdlPrintException(CORBA_ULong minor, const char* id, const char* scoped)
{
    CORBA_String_var err = CORBA_string_dup("interface repository error");

    switch(minor)
    {
    case OBMinorNoIntfRepos:
	err = CORBA_string_dup("interface repository is not available");
	break;

    case OBMinorIdExists:
	err = CORBA_string_dup("redefinition of repository id `");
	err += id;
	err += "'";
	break;
	
    case OBMinorNameExists:
	err = CORBA_string_dup("redefinition of `");
	err += scoped;
	err += "'";
	break;
	
    case OBMinorRepositoryDestroy:
	err = CORBA_string_dup("destroy() invoked on Repository object");
	break;
	
    case OBMinorPrimitiveDefDestroy:
	err = CORBA_string_dup("destroy() invoked on PrimitiveDef object");
	break;
	
    case OBMinorAttrExists:
	err = CORBA_string_dup("attribute `");
	err += scoped;
	err += "' is already defined";
	break;
	
    case OBMinorOperExists:
	err = CORBA_string_dup("operation `");
	err += scoped;
	err += "' is already defined";
	break;

    case OBMinorLookupAmbiguous:
	err = CORBA_string_dup("`");
	err += scoped;
	err += "' is ambiguous";
	break;
	
    case OBMinorAttrAmbiguous:
	err = CORBA_string_dup("attribute name collisions in base interfaces");
	break;
	
    case OBMinorOperAmbiguous:
	err = CORBA_string_dup("operation name collisions in base interfaces");
	break;
	
    case OBMinorIllegalRecursion:
	err = CORBA_string_dup("illegal recursion in `");
	err += scoped;
	err += "'";
	break;

    case OBMinorDupParameter:
	err = CORBA_string_dup("duplicate formal parameter in `");
	err += scoped;
	err += "'";
	break;
    }

    IdlError(err);
}

// ----------------------------------------------------------------------
// Add ID to IdlTopFileContent and IdlComments
// ----------------------------------------------------------------------

void
IdlAddID(const char* id)
{
    //
    // Add to top-level file content list if this is the top-level
    // file
    //
    if(IdlFileStack.length() <= 2)
	IdlTopFileContent -> append(id);
    
    //
    // Add comment for the repository id
    //
    if(strlen(IdlDocComment) > 0)
    {
	IdlComment comment;
	comment.id = id;
	comment.comment = IdlDocComment;
	IdlDocComment = CORBA_string_dup("");
	IdlComments -> append(comment);
    }
}
