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

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

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

#include <GenHTML.h>

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

//
// The keywords
//
const char* IdlHTMLGenerator::keyException_ = "exception";
const char* IdlHTMLGenerator::keyMember_ = "member";
const char* IdlHTMLGenerator::keyParam_ = "param";
const char* IdlHTMLGenerator::keyReturn_ = "return";
const char* IdlHTMLGenerator::keySee_ = "see";
const char* IdlHTMLGenerator::keySince_ = "since";
const char* IdlHTMLGenerator::keyAuthor_ = "author";
const char* IdlHTMLGenerator::keyVersion_ = "version";

// ----------------------------------------------------------------------
// Special HTML "Pretty-Print" class member implementation
// ----------------------------------------------------------------------

IdlHTMLGenerator::PrettyPrint::PrettyPrint(const char* prog,
					   const char* fullName,
					   const char* dir,
					   unsigned int num)
    : IdlPrettyPrint(out_, num)
{
    //
    // Create complete file name path
    //
    path_ = CORBA_string_dup(dir);
    if(strlen(path_) > 0)
	path_ += '/';
    path_ += fullName;
    path_ += ".html";

    //
    // Create the file
    //
    out_.open(path_);

    //
    // Write header
    //
    if(good())
    {
	*this << "<html>\n<head>\n\n";

	*this << "<!-- Generated by the ORBacus IDL-to-HTML translator "
	      << "-->\n"
	      << '\n'
	      << "<!-- Copyright (c) 1999 -->\n"
	      << "<!-- Object Oriented Concepts, Inc. -->\n"
	      << "<!-- Billerica, MA, USA -->\n"
	      << '\n'
	      << "<!-- All Rights Reserved -->\n"
	      << '\n'
	      << "<!-- Version: " << OBVersion << " -->\n"
	      << "<!-- License: " << OBLicense << " -->\n";

	*this << "\n<title>IDL Documentation for &quot;"
	      << fullName << "&quot;</title>";

	*this << "\n</head>\n<body>\n";
    }
    else
    {
	cerr << prog << ": can't open \"" << path_
	     << "\": " << strerror(errno) << endl;
    }
}

IdlHTMLGenerator::PrettyPrint::~PrettyPrint()
{
    *this << "\n<hr>";

    *this << "\n<p><em>";

    *this << "\nGenerated by the "
	  << "<a href=http://www.ooc.com/ob/>"
	  << "ORBacus</a> IDL-to-HTML translator ("
	  << OBLicense << ')';

    *this << "\n</em></p>";

    *this << "\n</body>\n</html>\n";
}

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

    IdlPrettyPrint::printString(s);

    const char* linkStart = strstr(s, "<a ");
    const char* linkEnd = strchr(s, '>');

    //
    // Don't count characters belonging to an HTML link
    //
    if(linkStart && linkEnd && linkEnd > linkStart)
	setPos(getPos() - (linkEnd - linkStart) - 5);
}

// ----------------------------------------------------------------------
// Print header comment
// ----------------------------------------------------------------------

void
IdlHTMLGenerator::comment(const char* id, IdlPrettyPrint& out)
{
    for(CORBA_ULong i = 0 ; i < commentSeq_.length() ; i++)
    {
	if(strcmp(id, commentSeq_[i].id) == 0)
	{
	    IdlStringSeq memberSeq;
	    IdlStringSeq exceptionSeq;
	    IdlStringSeq paramSeq;
	    IdlStringSeq returnSeq;
	    IdlStringSeq seeSeq;
	    IdlStringSeq sinceSeq;
	    IdlStringSeq authorSeq;
	    IdlStringSeq versionSeq;

	    CORBA_String_var comment = commentSeq_[i].comment;

	    const char* tok = tokenize(comment.inout());

	    while(tok)
	    {
		if(strncmp(tok, keyException_, strlen(keyException_)) == 0)
		{
		    exceptionSeq.append(tok + strlen(keyException_));
		}
		else if(strncmp(tok, keyMember_, strlen(keyMember_)) == 0)
		{
		    memberSeq.append(tok + strlen(keyMember_));
		}
		else if(strncmp(tok, keyParam_, strlen(keyParam_)) == 0)
		{
		    paramSeq.append(tok + strlen(keyParam_));
		}
		else if(strncmp(tok, keyReturn_, strlen(keyReturn_)) == 0)
		{
		    returnSeq.append(tok + strlen(keyReturn_));
		}
		else if(strncmp(tok, keySee_, strlen(keySee_)) == 0)
		{
		    seeSeq.append(tok + strlen(keySee_));
		}
		else if(strncmp(tok, keySince_, strlen(keySince_)) == 0)
		{
		    sinceSeq.append(tok + strlen(keySince_));
		}
		else if(strncmp(tok, keyAuthor_, strlen(keyAuthor_)) == 0)
		{
		    authorSeq.append(tok + strlen(keyAuthor_));
		}
		else if(strncmp(tok, keyVersion_, strlen(keyVersion_)) == 0)
		{
		    versionSeq.append(tok + strlen(keyVersion_));
		}

		tok = tokenize(0);
	    }

	    char* end = strchr(comment.inout(), '@');
	    if(end)
		*end = '\0';

	    out << "\n<p>" << comment << "\n</p>";

	    if(memberSeq.length() > 0)
	    {
		out << "\n<dl>\n<dt><b>Members:</b>";

		for(CORBA_ULong j = 0 ; j < memberSeq.length() ; j++)
		{
		    CORBA_String_var s = memberSeq[j];
		    const char* tok = strtok(s.inout(), " \t");
		    if(tok)
		    {
// 			out << "\n<dd><code><strong>" << tok
// 			    << "</strong></code>"
// 			    << " - " << tok + strlen(tok) + 1 << "</dd>";
			out << "\n<dd><code>" << tok
			    << "</code>"
			    << " - " << tok + strlen(tok) + 1 << "</dd>";
		    }
		}

		out << "\n</dl>";
	    }

	    if(paramSeq.length() > 0)
	    {
		out << "\n<dl>\n<dt><b>Parameters:</b>";

		for(CORBA_ULong j = 0 ; j < paramSeq.length() ; j++)
		{
		    CORBA_String_var s = paramSeq[j];
		    const char* tok = strtok(s.inout(), " \t");
		    if(tok)
		    {
// 			out << "\n<dd><code><strong>" << tok
// 			    << "</strong></code>"
// 			    << " - " << tok + strlen(tok) + 1 << "</dd>";
			out << "\n<dd><code>" << tok
			    << "</code>"
			    << " - " << tok + strlen(tok) + 1 << "</dd>";
		    }
		}

		out << "\n</dl>";
	    }

	    if(returnSeq.length() > 0)
	    {
		out << "\n<dl>\n<dt><b>Returns:</b>";

		for(CORBA_ULong j = 0 ; j < returnSeq.length() ; j++)
		{
		    out << "\n<dd>" << returnSeq[j] << "</dd>";
		}

		out << "\n</dl>";
	    }

	    if(exceptionSeq.length() > 0)
	    {
		out << "\n<dl>\n<dt><b>Raises:</b>";

		for(CORBA_ULong j = 0 ; j < exceptionSeq.length() ; j++)
		{
		    CORBA_String_var s = exceptionSeq[j];
		    const char* tok = strtok(s.inout(), " \t");
		    if(tok)
		    {
// 			out << "\n<dd><code><strong>" << tok
// 			    << "</strong></code>"
// 			    << " - " << tok + strlen(tok) + 1 << "</dd>";
			out << "\n<dd><code>" << tok
			    << "</code>"
			    << " - " << tok + strlen(tok) + 1 << "</dd>";
		    }
		}
		 
		out << "\n</dl>";
	    }

	    if(seeSeq.length() > 0)
	    {
		out << "\n<dl>\n<dt><b>See Also:</b>";

		for(CORBA_ULong j = 0 ; j < seeSeq.length() ; j++)
		{
		    out << "\n<dd>" << seeSeq[j] << "</dd>";
		}

		out << "\n</dl>";
	    }

	    if(sinceSeq.length() > 0)
	    {
		out << "\n<dl>\n<dt><b>Available Since:</b>";

		for(CORBA_ULong j = 0 ; j < sinceSeq.length() ; j++)
		{
		    out << "\n<dd>" << sinceSeq[j] << "</dd>";
		}

		out << "\n</dl>";
	    }

	    if(authorSeq.length() > 0)
	    {
		out << "\n<dl>\n<dt><b>Author:</b>";

		for(CORBA_ULong j = 0 ; j < authorSeq.length() ; j++)
		{
		    out << "\n<dd>" << authorSeq[j] << "</dd>";
		}

		out << "\n</dl>";
	    }

	    if(versionSeq.length() > 0)
	    {
		out << "\n<dl>\n<dt><b>Version:</b>";

		for(CORBA_ULong j = 0 ; j < versionSeq.length() ; j++)
		{
		    out << "\n<dd>" << versionSeq[j] << "</dd>";
		}

		out << "\n</dl>";
	    }

	    out << "<br>";

	    break;
	}
    }
}

// ----------------------------------------------------------------------
// Print intro
// ----------------------------------------------------------------------

void
IdlHTMLGenerator::intro(const char* id, const char* name, IdlPrettyPrint& out)
{
    CORBA_String_var link = getLink(id);

    out << "\n<dt><a href=\"" << link << "\">" << name << "</a>\n<br>\n<dd>";

    for(CORBA_ULong i = 0 ; i < commentSeq_.length() ; i++)
    {
	if(strcmp(id, commentSeq_[i].id) == 0)
	{
	    CORBA_String_var comment = commentSeq_[i].comment;

	    char* s = comment.inout();
	    char* end1;

	    while(true)
	    {
		end1 = strchr(s, '.');

		if(end1 == 0)
		    break;

		end1++;
		if(isspace(*end1))
		    break;

		s = end1;
	    }

	    char* end2 = strchr(comment.inout(), '@');

	    char* end;
	    if(end1 && end2)
		end = end1 < end2 ? end1 : end2;
	    else if(end1)
		end = end1;
	    else
		end = end2;

	    if(end)
		*end = '\0';

	    out << comment;

	    break;
	}
    }
}

// ----------------------------------------------------------------------
// Create type string
// ----------------------------------------------------------------------

char*
IdlHTMLGenerator::getTypeString(const char* scope, CORBA_TypeCode_ptr type,
			     GetType gt, const char* ident)
{
    //
    // Get result
    //
    CORBA_String_var result = CORBA_string_dup("???");
    switch(type -> kind())
    {
    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_any:
    case CORBA_tk_TypeCode:
    case CORBA_tk_Principal:
    {
	// **************************************************
	// Basic types (without strings)
	// **************************************************

	static const char* names[] =
	{
	    "void",
	    "short", "long",
	    "unsigned short", "unsigned long",
	    "float", "double",
	    "boolean", "char", "octet",
	    "any",
	    "TypeCode",
	    "Principal"
	};
	
	result = names[(int)(type -> kind()) - (int)CORBA_tk_void];

	break;
    }

    case CORBA_tk_objref:
    {
	// **************************************************
	// Object references (including pseudo object refereces)
        // **************************************************

	if(strcmp(type -> id(), "IDL:omg.org/CORBA/Object:1.0") == 0)
	{
	    result = CORBA_string_dup("Object");
	}
	else
	{
	    result = getAbsolute(type -> id());
	    IdlRemoveScope(scope, result.inout());
	}

	break;
    }
    
    case CORBA_tk_struct:
    case CORBA_tk_union:
    case CORBA_tk_except:
    case CORBA_tk_enum:
    case CORBA_tk_alias:
    {
	// **************************************************
	// Structs, unions, exceptions, enums
	// **************************************************

	result = getAbsolute(type -> id());
	IdlRemoveScope(scope, result.inout());

	break;
    }

    case CORBA_tk_string:
    {
	// **************************************************
	// Strings
	// **************************************************

	result = CORBA_string_dup("string");

	if(type -> length())
	{
	    result += '<';
	    result += type -> length();
	    result += '>';
	}

	break;
    }

    case CORBA_tk_sequence:
    {
	// **************************************************
	// Sequences
	// **************************************************

	CORBA_TypeCode_var contentType = type -> content_type();
	CORBA_String_var contentTypeString = getTypeString(scope, contentType);
	
	switch(contentType -> kind())
	{
	case CORBA_tk_null:
	case CORBA_tk_void:
	    assert(false);
	    break;
	    
	default:

	    result = CORBA_string_dup("sequence");
	    result += "&lt;";
	    result += contentTypeString;
	    
	    if(type -> length())
	    {
		result += ", ";
		result += type -> length();
	    }
	    
	    result += "&gt;";
	    
	    break;
	}

	break;
    }

    case CORBA_tk_array:
    {
	// **************************************************
	// Arrays
	// **************************************************

	OBFixSeq<CORBA_ULong> sizes;
	
	CORBA_TypeCode_var contentType = CORBA_TypeCode::_duplicate(type);
	
	while(contentType -> kind() == CORBA_tk_array)
	{
	    sizes.append(contentType -> length());
	    contentType = contentType -> content_type();
	}
	
	CORBA_String_var array = CORBA_string_dup("");
	
	for(CORBA_ULong i = 0 ; i < sizes.length() ; i++)
	{
	    array += '[';
	    array += sizes[i];
	    array += ']';
	}
	
	result = getTypeString(scope, contentType);
	result += ' ';
	result += ident;
	result += array;

	break;
    }

    case CORBA_tk_null:
	assert(false);
	break;
    }

    //
    // Add link
    //
    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_any:
    case CORBA_tk_TypeCode:
    case CORBA_tk_Principal:
    case CORBA_tk_sequence:
    case CORBA_tk_array:
    case CORBA_tk_string:
	break;
	
    case CORBA_tk_objref:
    case CORBA_tk_struct:
    case CORBA_tk_union:
    case CORBA_tk_except:
    case CORBA_tk_enum:
    case CORBA_tk_alias:
    {
	if(!(type -> kind() == CORBA_tk_objref &&
	     strcmp(type -> id(), "IDL:omg.org/CORBA/Object:1.0") == 0))
	{
	    CORBA_String_var tmp = result._retn();
	    
	    CORBA_String_var link = getLink(type -> id());
	    
	    result = CORBA_string_dup("<a href=\"");
	    result += link;
	    result += "\">";
	    result += tmp;
	    result += "</a>";
	}

	break;
    }
    }
    
    //
    // Add in, inout, or out
    //
    switch(gt)
    {
    case GetTypeNormal:
	break;

    case GetTypeIn:
    {
	CORBA_String_var tmp = result._retn();
	result = CORBA_string_dup("in ");
	result += tmp;
	break;
    }
    
    case GetTypeInOut:
    {
	CORBA_String_var tmp = result._retn();
	result = CORBA_string_dup("inout ");
	result += tmp;
	break;
    }
    
    case GetTypeOut:
    {
	CORBA_String_var tmp = result._retn();
	result = CORBA_string_dup("out ");
	result += tmp;
	break;
    }
    }

    //
    // Add identifier
    //
    if(type -> kind() != CORBA_tk_array)
    {
	if(ident && strlen(result))
	{
	    result += ' ';
	    result += ident;
	}
    }

    return result._retn();
}

// ----------------------------------------------------------------------
// Get absolute name
// ----------------------------------------------------------------------

char*
IdlHTMLGenerator::getAbsolute(CORBA_Contained_ptr contained)
{
    CORBA_ScopedName_var abs = contained -> absolute_name();
    return CORBA_string_dup(abs.in() + 2); // Remove the "::"
}

char*
IdlHTMLGenerator::getAbsolute(const char* id)
{
    for(CORBA_ULong i = 0 ; i < absSeq_.length() ; i += 2)
    {
	if(strcmp(absSeq_[i], id) == 0)
	    return CORBA_string_dup(absSeq_[i + 1]);
    }
    
    CORBA_Contained_var contained = repository_ -> lookup_id(id);
    assert(!CORBA_is_nil(contained));
    
    CORBA_ULong len = absSeq_.length();
    absSeq_.length(len + 2);
    absSeq_[len] = id;
    absSeq_[len + 1] = getAbsolute(contained);
    return CORBA_string_dup(absSeq_[len + 1]);
}

// ----------------------------------------------------------------------
// Fix absolute path name, i.e. replace "::" by "."
// ----------------------------------------------------------------------

char*
IdlHTMLGenerator::fixAbsolute(const char* abs)
{
    CORBA_String_var str = abs;
    CORBA_String_var result;

    char* start = str.inout();
    char* end;

    while(true)
    {
	end = strchr(start, ':');

	if(end)
	{
	    *end = '\0';
	    result += start;
	    result += '.';
	    start = end + 2;
	}
	else
	{
	    result += start;
	    break;
	}
    }

    return result._retn();
}

// ----------------------------------------------------------------------
// Get link
// ----------------------------------------------------------------------

char*
IdlHTMLGenerator::getLink(const char* id)
{
    for(CORBA_ULong i = 0 ; i < linkSeq_.length() ; i += 2)
    {
	if(strcmp(linkSeq_[i], id) == 0)
	    return CORBA_string_dup(linkSeq_[i + 1]);
    }

    CORBA_String_var absolute = getAbsolute(id);
    absolute = fixAbsolute(absolute);

    CORBA_String_var link;

    CORBA_Contained_var contained = repository_ -> lookup_id(id);
    assert(!CORBA_is_nil(contained));
    
    CORBA_DefinitionKind defKind = contained -> def_kind();
    if(defKind == CORBA_dk_Module || defKind == CORBA_dk_Interface)
    {
	link = absolute._retn();
	link += ".html";
    }
    else
    {
	char* name = strrchr(absolute.inout(), '.');
	if(name)
	{
	    name++;
	    assert(*name != '\0');
	    *(name - 1) = '\0';
	    link = CORBA_string_dup(absolute);
	    link += ".html#";
	    link += name;
	}
	else
	{
	    link = CORBA_string_dup(top_);
	    link += ".html#";
	    link += absolute;
	}
    }

    CORBA_ULong len = linkSeq_.length();
    linkSeq_.length(len + 2);
    linkSeq_[len] = id;
    linkSeq_[len + 1] = link;
    return link._retn();
}

// ----------------------------------------------------------------------
// Get scope
// ----------------------------------------------------------------------

void
IdlHTMLGenerator::getScope(CORBA_Container_ptr container, char*& scope)
{
    CORBA_Contained_var contained = CORBA_Contained::_narrow(container);
    if(CORBA_is_nil(contained))
	scope = CORBA_string_dup("");
    else
    {
	CORBA_ScopedName_var s = contained -> absolute_name();
	scope = CORBA_string_alloc(strlen(s));
	strcpy(scope, s.in() + 2); // Remove the leading "::"
	strcat(scope, "::"); // Add a trainling "::"
    }
}

// ----------------------------------------------------------------------
// Print union labels
// ----------------------------------------------------------------------

void
IdlHTMLGenerator::printUnionLabels(const IdlUnionMemberInfo& info,
				   IdlPrettyPrint& out,
				   CORBA_TypeCode_ptr type)
{
    if(!CORBA_is_nil(info.type))
    {
	out.dec();

	if(info.isDefault)
	    out << "\ndefault:";
	else
	{
	    CORBA_ULong i;
	    
	    for(i = 0 ; i < info.pLabels.length() ; i++)
	    {
		out << "\ncase ";

		switch(type -> kind())
		{
		case CORBA_tk_char:
		{
		    char label = (char)info.pLabels[i];
		    CORBA_String_var s = CORBA_string_dup(&label);
		    IdlAddEscapes(s.inout(), false);

		    out << '\'' << s << '\'';

		    break;
		}

		case CORBA_tk_boolean:
		{
		    if(info.pLabels[i])
			out << "TRUE";
		    else
			out << "FALSE";

		    break;
		}

		case CORBA_tk_enum:
		{
		    CORBA_Identifier_var memberName =
			type -> member_name(info.pLabels[i]);
		    out << memberName;

		    break;
		}

		default:
		    out << info.pLabels[i];
		}

		out << ':';
	    }
	    
	    for(i = 0 ; i < info.nLabels.length() ; i++)
		out << "\ncase -" << info.nLabels[i] << ':';
	}

	out.inc();
    }
}

// ----------------------------------------------------------------------
// Tokenize
// ----------------------------------------------------------------------

char*
IdlHTMLGenerator::tokenize(char* s1)
{
    static char* str;

    if(s1)
	str = strchr(s1, '@');

    while(str)
    {
	char* ret = str + 1;

	//
	// Create a new token for reserved keywords only
	//
	for(CORBA_ULong i = 0 ; i < keywordSeq_.length() ; i++)
	{
	    if(isKeyword(ret))
	    {
		str = strchr(ret, '@');
		while(str)
		{
		    if(isKeyword(str + 1))
		    {
			*str = 0;
			return ret;
		    } 

		    str = strchr(str + 1, '@');
		}

		return ret;
	    }
	}

	str = strchr(ret, '@');
    }

    return 0;
}

bool
IdlHTMLGenerator::isKeyword(const char* str)
{
    for(CORBA_ULong i = 0 ; i < keywordSeq_.length() ; i++)
    {
	CORBA_ULong len = strlen(keywordSeq_[i]);
	if(strncmp(str, keywordSeq_[i], len) == 0)
	{
	    //
	    // Check for space or tab after keyword
	    //
	    char c = str[len];
	    return c == ' ' || c == '\t';
	}
    }

    return false;
}
