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

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

#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <sys/stat.h>

// ----------------------------------------------------------------------
// Pretty-Print private and protected members
// ----------------------------------------------------------------------

void
IdlPrettyPrint::nl()
{
    out_ << '\n';
    
    for(unsigned int i = 0 ; i < indent_ ; i++)
	out_ << ' ';
    
    pos_ = indent_;
}

// ----------------------------------------------------------------------
// Pretty-Print public members
// ----------------------------------------------------------------------

void
IdlPrettyPrint::start()
{
    *this << "\n{";
    inc();
}

void
IdlPrettyPrint::end()
{
    dec();
    *this << "\n}";
}

void
IdlPrettyPrint::sep()
{
    if(ignoreNextSep_)
	ignoreNextSep_ = false;
    else
	out_ << '\n';
}

void
IdlPrettyPrint::ignoreNextSep(bool ignore)
{
    ignoreNextSep_ = ignore;
}

void
IdlPrettyPrint::printChar(char c)
{
    if(c == '\r')
    {
	// Do nothing
    }
    else if(c == '\n')
    {
	nl();
    }
    else if(c == '\t')
    {
	while(pos_ % 8)
	{
	    out_ << ' ';
	    pos_++;
	}
    }
    else
    {
	out_ << c;
	pos_++;
    }
}

void
IdlPrettyPrint::printString(const char* s)
{
    if(!s)
	return;

    while(*s)
    {
	printChar(*s);
	s++;
    }
}

// ----------------------------------------------------------------------
// Pretty-Print output operators
// ----------------------------------------------------------------------

IdlPrettyPrint&
operator<<(IdlPrettyPrint& pp, char c)
{
    pp.printChar(c);
    return pp;
}

IdlPrettyPrint&
operator<<(IdlPrettyPrint& pp, const char* s)
{
    pp.printString(s);
    return pp;
}

IdlPrettyPrint&
operator<<(IdlPrettyPrint& pp, short val)
{
    return pp << (long)val;
}

IdlPrettyPrint&
operator<<(IdlPrettyPrint& pp, int val)
{
    return pp << (long)val;
}

IdlPrettyPrint&
operator<<(IdlPrettyPrint& pp, long val)
{
    char s[1024];
    sprintf(s, "%ld", val);
    pp.printString(s);
    return pp;
}

IdlPrettyPrint&
operator<<(IdlPrettyPrint& pp, unsigned short val)
{
    return pp << (unsigned long)val;
}

IdlPrettyPrint&
operator<<(IdlPrettyPrint& pp, unsigned int val)
{
    return pp << (unsigned long)val;
}

IdlPrettyPrint&
operator<<(IdlPrettyPrint& pp, unsigned long val)
{
    char s[1024];
    sprintf(s, "%ld", val);
    pp.printString(s);
    return pp;
}

IdlPrettyPrint&
operator<<(IdlPrettyPrint& pp, float val)
{
    return pp << (double)val;
}

IdlPrettyPrint&
operator<<(IdlPrettyPrint& pp, double val)
{
    char s[1024];
    sprintf(s, "%.100g", val);
    pp.printString(s);
    return pp;
}

// ----------------------------------------------------------------------
// Remove scope from absolute name
// ----------------------------------------------------------------------

void
IdlRemoveScope(const char* scope, char*& absolute)
{
    assert_nca(scope, OBNCANullString);
    assert_nca(absolute, OBNCANullString);

    if(strlen(scope) > 0)
    {
	assert(scope[strlen(scope) - 1] == ':');
	assert(scope[strlen(scope) - 2] == ':');
	
	if(strncmp(scope, absolute, strlen(scope)) == 0)
	{
	    char* a = CORBA_string_dup(absolute + strlen(scope));
	    CORBA_string_free(absolute);
	    absolute = a;
	}
	else if(strchr(scope, ':') != scope + strlen(scope) - 2)
	{
	    CORBA_String_var s = scope;
	    s[strlen(s) - 2] = '\0';
	    char* s2 = (char*)strrchr(s, ':');
	    if(s2)
	    {
		assert(*(s2 - 1) == ':');
		*(s2 + 1) = '\0';
		IdlRemoveScope(s, absolute);
	    }
	}
    }
}

// ----------------------------------------------------------------------
// Get anonymous name from template name
// ----------------------------------------------------------------------

char*
IdlGetAnonName(const char* absolute)
{
    CORBA_String_var name;
    for(CORBA_ULong j = 0 ; j < strlen(absolute) ; j++)
    {
	char c = absolute[j];
	
	if(c == ':')
	{
	    name += '_';
	    j++;
	}
	else if(c == '<' || c == '[' || c == ',')
	{
	    name += '_';
	}
	else if(c == '_')
	{
	    name += "__";
	}
	else if(isalnum(c))
	{
	    name += c;
	}
    }

    return name._retn();
}

// ----------------------------------------------------------------------
// Get original type, i.e. translate aliases to the real type
// ----------------------------------------------------------------------

CORBA_IDLType_ptr
IdlGetOrigIDLType(CORBA_IDLType_ptr idlType)
{
    CORBA_IDLType_var origType = CORBA_IDLType::_duplicate(idlType);
    
    while(true)
    {
	CORBA_AliasDef_var alias = CORBA_AliasDef::_narrow(origType);
	
	if(CORBA_is_nil(alias))
	    break;
	else
	    origType = alias -> original_type_def();
    }
    
    return CORBA_IDLType::_duplicate(origType);
}

// ----------------------------------------------------------------------
// Check for variable size type
// ----------------------------------------------------------------------

bool
IdlIsVariable(CORBA_TypeCode_ptr t)
{
    CORBA_TypeCode_var type = OBGetOrigType(t);

    switch(type -> kind())
    {
    case CORBA_tk_null:
    case CORBA_tk_void:
    case CORBA_tk_short:
    case CORBA_tk_long:
    case CORBA_tk_ushort:
    case CORBA_tk_ulong:
    case CORBA_tk_float:
    case CORBA_tk_double:
    case CORBA_tk_boolean:
    case CORBA_tk_char:
    case CORBA_tk_octet:
    case CORBA_tk_enum:
	return false;

    case CORBA_tk_any:
    case CORBA_tk_TypeCode:
    case CORBA_tk_Principal:
    case CORBA_tk_objref:
    case CORBA_tk_string:
    case CORBA_tk_sequence:
    case CORBA_tk_except:
	return true;

    case CORBA_tk_struct:
    case CORBA_tk_union:
    {
	for(CORBA_ULong i = 0 ; i < type -> member_count() ; i++)
	{
	    CORBA_TypeCode_var memberType = type -> member_type(i);
	    if(IdlIsVariable(memberType))
		return true;
	}
	
	return false;
    }

    case CORBA_tk_array:
    {
	CORBA_TypeCode_var contentType = type -> content_type();

	if(IdlIsVariable(contentType))
	    return true;
	else
	    return false;
    }

    case CORBA_tk_alias: // No alias possible here,
	break;     // this is to keep the compiler happy
    }

    return false; // This is to keep the compiler happy
}

// ----------------------------------------------------------------------
// Get TypeCode converted to ASCII
// Output TypeCode converted to ASCII
// ----------------------------------------------------------------------

char*
IdlGetAsciiTypeCode(CORBA_TypeCode_ptr tc)
{
    CORBA_ULong count = 0;
    OBMarshalCount(OBEndian, count);
    OBMarshalCount(tc, count);

    CORBA_Octet* const buf = new CORBA_Octet[count];

    for(CORBA_ULong i = 0 ; i < count ; i++)
	buf[i] = 0;

    CORBA_Octet* oct = buf;
    OBMarshal(OBEndian, oct);
    OBMarshal(tc, oct);

    char* typeCodeStr = OBOctetsToAscii(buf, count);

    delete [] buf;

    return typeCodeStr;
}

void
IdlOutputAsciiTypeCode(CORBA_TypeCode_ptr tc, IdlPrettyPrint& out)
{
    CORBA_String_var typeCodeStr = IdlGetAsciiTypeCode(tc);

    unsigned int indent = out.getIndent();
    out.setIndent(0);

    CORBA_ULong j = 0;

    do
    {
	out << "\n\"";
	for(CORBA_ULong i = 0 ; i < 77 && typeCodeStr[j] != '\0' ; i++)
	    out << typeCodeStr[j++];
	out << '"';
    }
    while(typeCodeStr[j] != '\0');

    out << '\n';
    
    out.setIndent(indent);
}

// ----------------------------------------------------------------------
// Convert path name to identifier
// ----------------------------------------------------------------------

char*
IdlPathToIdent(const char* s)
{
    CORBA_String_var ident;
    
    for(CORBA_ULong i = 0 ; i < strlen(s) ; i++)
    {
	char c = s[i];
	
	if(c == '.' || c == '/')
	    c = '_';
	
	if(isalnum(c) || c == '_')
	    ident += c;
    }
    
    return ident._retn();
}

// ----------------------------------------------------------------------
// Check if union has an implicit default
// ----------------------------------------------------------------------

bool
IdlImplicitUnionDefault(CORBA_TypeCode_ptr type)
{
    CORBA_TypeCode_var origType = OBGetOrigType(type);
    assert(origType -> kind() == CORBA_tk_union);

    bool result = false;

    if(origType -> default_index() == -1)
    {
	result = true;
	
	CORBA_TypeCode_var tc = origType -> discriminator_type();
	tc = OBGetOrigType(tc);
	
	if(tc -> kind() == CORBA_tk_char)
	    if(origType -> member_count() == 256)
		result = false;
	
	if(tc -> kind() == CORBA_tk_boolean)
	    if(origType -> member_count() == 2)
		result = false;
	
	if(tc -> kind() == CORBA_tk_enum)
	    if(origType -> member_count() == tc -> member_count())
		result = false;
    }

    return result;
}

// ----------------------------------------------------------------------
// Get union info
// ----------------------------------------------------------------------

IdlUnionMemberInfoSeq*
IdlGetUnionInfo(CORBA_TypeCode_ptr type)
{
    CORBA_TypeCode_var origType = OBGetOrigType(type);
    assert(origType -> kind() == CORBA_tk_union);
    
    CORBA_TypeCode_var discType = origType -> discriminator_type();
    CORBA_TypeCode_var origDiscType = OBGetOrigType(discType);

    IdlUnionMemberInfoSeq_var seq =
	new IdlUnionMemberInfoSeq(origType -> member_count());
    
    bool hasDefault = false;
    CORBA_ULong defaultIndex;

    CORBA_ULong i, j, k;
    
    for(i = 0 ; i < origType -> member_count() ; i++)
    {
	CORBA_Identifier_var name = origType -> member_name(i);
	
	if(i > 0)
	{
	    CORBA_Identifier_var s = origType -> member_name(i - 1);

	    if(strcmp(s, name) != 0)
	    {
		CORBA_ULong len = seq -> length();
		seq -> length(len + 1);
		seq[len].name = name;
		seq[len].type = origType -> member_type(i);
		seq[len].isDefault = false;
	    }
	    else
	    {
		assert(seq[seq -> length() - 1].nLabels.length() > 0 ||
		       seq[seq -> length() - 1].pLabels.length() > 0 ||
		       seq[seq -> length() - 1].isDefault);
	    }
	}
	else
	{
	    CORBA_ULong len = seq -> length();
	    seq -> length(len + 1);
	    seq[len].name = name;
	    seq[len].type = origType -> member_type(i);
	    seq[len].isDefault = false;
	}

	IdlUnionMemberInfo& info = seq[seq -> length() - 1];

	CORBA_Any_var label = origType -> member_label(i);
	CORBA_TypeCode_var labelType = label -> type();
	CORBA_TypeCode_var origLabelType = OBGetOrigType(labelType);

	switch(origLabelType -> kind())
	{
	case CORBA_tk_short:
	{
	    CORBA_Short val;
	    CORBA_Boolean b = label >>= val;
	    assert(b);
	    if(val >= 0)
		info.pLabels.append((CORBA_ULong)val);
	    else
		info.nLabels.append((CORBA_ULong)-val);
	    break;
	}

	case CORBA_tk_ushort:
	{
	    CORBA_UShort val;
	    CORBA_Boolean b = label >>= val;
	    assert(b);
	    info.pLabels.append((CORBA_ULong)val);
	    break;
	}

	case CORBA_tk_long:
	{
	    CORBA_Long val;
	    CORBA_Boolean b = label >>= val;
	    assert(b);
	    if(val >= 0)
		info.pLabels.append((CORBA_ULong)val);
	    else
		info.nLabels.append((CORBA_ULong)-val);
	    break;
	}

	case CORBA_tk_ulong:
	{
	    CORBA_ULong val;
	    CORBA_Boolean b = label >>= val;
	    assert(b);
	    info.pLabels.append((CORBA_ULong)val);
	    break;
	}

	case CORBA_tk_enum:
	{
	    CORBA_ULong val = *(CORBA_ULong*)(label -> value());
	    info.pLabels.append((CORBA_ULong)val);
	    break;
	}

	case CORBA_tk_char:
	{
	    CORBA_Char val;
	    CORBA_Boolean b = label >>= CORBA_Any::to_char(val);
	    assert(b);
	    info.pLabels.append((CORBA_ULong)val);
	    break;
	}

	case CORBA_tk_boolean:
	{
	    CORBA_Boolean val;
	    CORBA_Boolean b = label >>= CORBA_Any::to_boolean(val);
	    assert(b);
	    info.pLabels.append((CORBA_ULong)val);
	    break;
	}

	case CORBA_tk_octet:
	    assert(hasDefault == false);
	    hasDefault = true;
	    defaultIndex = seq -> length() - 1;
	    info.isDefault = true;
	    break;

	default:
	    assert(false);
	    break;
	}
    }

    CORBA_ULong min, max;
    
    switch(origDiscType -> kind())
    {
    case CORBA_tk_short:
	min = 32768;
	max = 32767;
	break;
	
    case CORBA_tk_ushort:
	min = 0;
	max = 65535;
	break;
	
    case CORBA_tk_long:
	min = 2147483648U;
	max = 2147483647U;
	break;
	
    case CORBA_tk_ulong:
	min = 0;
	max = 4294967294U;
	//max = 4294967295U;
	break;
	
    case CORBA_tk_enum:
	min = 0;
	max = origDiscType -> member_count() - 1;
	break;
	
    case CORBA_tk_char:
	min = 0;
	max = 255;
	break;
	
    case CORBA_tk_boolean:
	min = 0;
	max = 1;
	break;
	
    default:
	assert(false);
	break;
    }

    bool hasPDefault = false;
    bool hasNDefault = false;
    CORBA_ULong defPLabel;
    CORBA_ULong defNLabel;
    
    for(i = 0 ; i <= max ; i++)
    {
	bool ok = true;
	
	for(j = 0 ; ok && j < seq -> length() ; j++)
	    for(k = 0 ; ok && k < seq[j].pLabels.length() ; k++)
		if(seq[j].pLabels[k] == i)
		    ok = false;
	
	if(ok)
	{
	    hasPDefault = true;
	    defPLabel = i;
	    break;
	}
    }
    
    for(i = 1 ; i <= min ; i++)
    {
	bool ok = true;
	
	for(j = 0 ; ok && j < seq -> length() ; j++)
	    for(k = 0 ; ok && k < seq[j].nLabels.length() ; k++)
		if(seq[j].nLabels[k] == i)
		    ok = false;
	
	if(ok)
	{
	    hasNDefault = true;
	    defNLabel = i;
	    break;
	}
    }

    if(hasPDefault || hasNDefault)
    {
	if(!hasDefault)
	{
	    CORBA_ULong len = seq -> length();
	    seq -> length(len + 1);
	    seq[len].name = CORBA_string_dup("");
	    seq[len].isDefault = true;

	    hasDefault = true;
	    defaultIndex = len;
	}

	// This is legal:
	//
	//     union uni switch(short)
        //     {
	//     case 1 :
        //     default : long value;
	//     };
	//
        // Therefore the following asserts are wrong:
	//
	//assert(seq[defaultIndex].pLabels.length() == 0);
	//assert(seq[defaultIndex].nLabels.length() == 0);
	assert(seq[defaultIndex].isDefault);
	
	if(hasPDefault)
	    seq[defaultIndex].pLabels.append(defPLabel);
	
	if(hasNDefault)
	    seq[defaultIndex].nLabels.append(defNLabel);
    }
    else
    {
	if(hasDefault)
	{
	    seq -> remove(defaultIndex);
	    hasDefault = false;
	};
    };

    return seq._retn();
};

// ----------------------------------------------------------------------
// Get all base interfaces
// ----------------------------------------------------------------------

void
IdlGetAllBaseInterfaces(CORBA_InterfaceDef_ptr interf,
			CORBA_InterfaceDefSeq& seq)
{
    CORBA_ULong i, j;

    CORBA_InterfaceDefSeq_var baseSeq = interf -> base_interfaces();
    for(i = 0 ; i < baseSeq -> length() ; i++)
    {
	CORBA_RepositoryId_var baseId = baseSeq[i] -> id(); 
	for(j = 0 ; j < seq.length() ; j++)
	{
	    CORBA_RepositoryId_var id = seq[j] -> id(); 
	    if(strcmp(baseId, id) == 0)
		break;
	}

	if(j == seq.length())
	{
	    seq.length(seq.length() + 1);
	    seq[seq.length() - 1] = CORBA_InterfaceDef::_duplicate(baseSeq[i]);
	    IdlGetAllBaseInterfaces(baseSeq[i], seq);
	}
    }
}


// ----------------------------------------------------------------------
// Get all method names
// ----------------------------------------------------------------------

IdlStringSeq*
IdlGetMethodNames(const CORBA_ContainedSeq& attributes,
		  const CORBA_ContainedSeq& operations)
{
    IdlStringSeq_var names = new IdlStringSeq;
    CORBA_ULong i, j;

    //
    // Add each attribute to the method table
    //
    for(i = 0 ; i < attributes.length() ; i++)
    {
        CORBA_Contained::Description_var contDesc =
	    attributes[i] -> describe();
	CORBA_AttributeDescription* desc;
	CORBA_Boolean b = (contDesc -> value) >>= desc;
	assert(b);

	CORBA_String_var name = CORBA_string_dup("_get_");
        name += desc -> name;
	names -> append(name._retn());

        if(desc -> mode == CORBA_ATTR_NORMAL)
        {
	    CORBA_String_var name = CORBA_string_dup("_set_");
	    name += desc -> name;
	    names -> append(name._retn());
        }
    }

    //
    // Add each operation to the method table
    //
    for(i = 0 ; i < operations.length() ; i++)
    {
        CORBA_Contained::Description_var contDesc =
	    operations[i] -> describe();
	CORBA_OperationDescription* desc;
	CORBA_Boolean b = (contDesc -> value) >>= desc;
	assert(b);

	names -> append(desc -> name);
    }

    //
    // Sort names (bubblesort)
    //
    for(i = 0 ; i < names -> length() ; i++)
    {
	for(j = i ; j < names -> length() ; j++)
	{
	    if(strcmp(names[i], names[j]) > 0)
	    {
		CORBA_String_var name = names[i]._retn();
		names[i] = names[j]._retn();
		names[j] = name._retn();
	    }
	}
    }

    return names._retn();
}

// ----------------------------------------------------------------------
// Check for C++ proprocessor
// ----------------------------------------------------------------------

bool
IdlCheckForCPP(const char* progName, const char* cppCmd)
{
    //
    // Strip parameters
    //
    CORBA_String_var exeCmd = CORBA_string_dup("");
    const char* cmd = cppCmd;
    while(*cmd && *cmd != ' ')
	exeCmd += *cmd++;

    bool status = false;

#ifdef WIN32

    char buf[MAX_PATH];
    char* tmp;

    exeCmd += ".exe";
    status = SearchPath(0, exeCmd, 0, MAX_PATH, buf, &tmp) != 0;

#else

    struct stat buf;

    //
    // Search for preprocessor in current directory
    //
    if(stat(exeCmd, &buf) == 0)
    {
	if(buf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))
	    status = true;	    
    }

    if(!status)
    {
	//
	// Search for preprocessor in PATH
	//
	char* path = getenv("PATH");
	if(path)
	{
	    char* s = strdup(path);

	    char* tok = strtok(s, ":");
	    while(tok)
	    {
		CORBA_String_var str = CORBA_string_dup(tok);
		str += "/";
		str += exeCmd;

		//
		// Check if preprocessor exists and whether it is executable
		//
		if(stat(str, &buf) == 0)
		{
		    if(buf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))
		    {
			status = true;
			break;
		    }
		}
	    
		tok = strtok(0, ":");
	    }

	    free(s);
	}
    }

#endif

    if(!status)
	cerr << progName << ": couldn't find " << exeCmd << endl;

    return status;
}
