// **********************************************************************
//
// 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 <GenRTF.h>

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

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

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

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

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

    //
    // Write header
    //
    if(good())
    {
	out_ << "{";

	// Header
	out_ << "\\rtf1" // RTF version 1
	     << "\\ansi" // ANSI character set
	     << "\\deff0";

	// Font definitions
	out_ << "\n{\\fonttbl"
	     << "\n{\\f0\\fcharset0\\f" << bodyFont << ';'
	     << "\n}"
	     << "\n{\\f1\\fcharset0\\f" << literalFont << ';'
	     << "\n}"
	     << "\n{\\f2\\fcharset0\\f" << titleFont << ';'
	     << "\n}"
	     << "\n{\\f3\\fcharset0\\f" << headingFont << ';'
	     << "\n}"
	     << "\n{\\f4\\fcharset2\\froman Symbol;"
	     << "\n}"
	     << "\n}";

	out_ << "\n{\\info";

	out_ << "\n{\\title\nDocumentation for \"" << fullName << "\"\n}";

	out_ << "\n{\\comment"
	     << "\nGenerated by the "
	     << "ORBacus IDL-to-RTF translator\n"
	     << "Version: " << OBVersion << '\n'
	     << "License: " << OBLicense
	     << "\n}";

	out_ << "\n}\n";
    }
    else
    {
	cerr << prog << ": can't open \"" << path_
	     << "\": " << strerror(errno) << endl;
    }
}

IdlRTFGenerator::PrettyPrint::~PrettyPrint()
{
    out_ << "\n}\n";
}

void
IdlRTFGenerator::PrettyPrint::nl()
{
    out_ << "\\par\\pard\n";

    if(indent_)
	out_ << "\\li" << 72 * indent_ << ' ';
    
    pos_ = indent_;
}

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

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

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

    IdlPrettyPrint::printString(s);

    const char* fontStart = strstr(s, "\\f");
    const char* fontEnd = strchr(s, ' ');

    //
    // Don't count font formatting characters
    //
    if(fontStart && fontEnd && fontEnd > fontStart)
        setPos(getPos() - (fontEnd - fontStart) - 5);
}

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

void
IdlRTFGenerator::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';
	    paragraph(comment, out);
	    out << '\n';

	    if(memberSeq.length() > 0)
	    {
		out << '\n' << boldon_ << "Members:" << boldoff_;

		out.inc();

		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\\b" << literal_ << tok << "\\b0" << body_
// 			    << " - ";
			out << '\n' << literal_ << tok << body_
			    << " - ";
			paragraph(tok + strlen(tok) + 1, out);
		    }
		}

		out.dec();
		out << '\n';
	    }

	    if(paramSeq.length() > 0)
	    {
		out << '\n' << boldon_ << "Parameters:" << boldoff_ ;

		out.inc();

		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\\b" << literal_ << tok << "\\b0" << body_
// 			    << " - ";
			out << '\n' << literal_ << tok << body_
			    << " - ";
			paragraph(tok + strlen(tok) + 1, out);
		    }
		}

		out.dec();
		out << '\n';
	    }

	    if(returnSeq.length() > 0)
	    {
		out << '\n' << boldon_ << "Returns:" << boldoff_;

		out.inc();

		for(CORBA_ULong j = 0 ; j < returnSeq.length() ; j++)
		{
		    out << '\n';
		    paragraph(returnSeq[j], out);
		}

		out.dec();
		out << '\n';
	    }

	    if(exceptionSeq.length() > 0)
	    {
		out << '\n' << boldon_ << "Raises:" << boldoff_;

		out.inc();

		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\\b" << literal_ << tok << "\\b0" << body_
// 			    << " - ";
			out << '\n' << literal_ << tok << body_
			    << " - ";
			paragraph(tok + strlen(tok) + 1, out);
		    }
		}

		out.dec();		 
		out << '\n';
	    }

	    if(seeSeq.length() > 0)
	    {
		out << '\n' << boldon_ << "See Also:" << boldoff_;

		out.inc();

		for(CORBA_ULong j = 0 ; j < seeSeq.length() ; j++)
		{
		    out << '\n';
		    paragraph(seeSeq[j], out);
		}

		out.dec();
		out << '\n';
	    }

	    if(sinceSeq.length() > 0)
	    {
		out << '\n' << boldon_ << "Available Since:" << boldoff_;

		out.inc();

		for(CORBA_ULong j = 0 ; j < sinceSeq.length() ; j++)
		{
		    out << '\n';
		    paragraph(sinceSeq[j], out);
		}

		out.dec();
		out << '\n';
	    }

	    if(authorSeq.length() > 0)
	    {
		out << '\n' << boldon_ << "Author:" << boldoff_;

		out.inc();

		for(CORBA_ULong j = 0 ; j < authorSeq.length() ; j++)
		{
		    out << '\n';
		    paragraph(authorSeq[j], out);
		}

		out.dec();
		out << '\n';
	    }

	    if(versionSeq.length() > 0)
	    {
		out << '\n' << boldon_ << "Version:" << boldoff_;

		out.inc();

		for(CORBA_ULong j = 0 ; j < versionSeq.length() ; j++)
		{
		    out << '\n';
		    paragraph(versionSeq[j], out);
		}

		out.dec();
		out << '\n';
	    }

	    break;
	}
    }
}

// ----------------------------------------------------------------------
// Print paragraph, replace HTML attributes by RTF equivalents
// ----------------------------------------------------------------------

void
IdlRTFGenerator::paragraph(const char* para, IdlPrettyPrint& out)
{
    CORBA_String_var str = CORBA_string_dup(para);
    CORBA_Long i, j; // Must be signed

    //
    // Remove tags that are explicitly not supported
    //
    str = replace(str, "<blink>", "");
    str = replace(str, "</blink>", "");
    str = replace(str, "<big>", "");
    str = replace(str, "</big>", "");
    str = replace(str, "<small>", "");
    str = replace(str, "</small>", "");

    //
    // Replace tabs and newlines, linefeeds, etc. by spaces. This has
    // to be done before replacing HTML attributes with RTF commands
    // that have a newline.
    //
    for(i = 0 ; i < (CORBA_Long)strlen(str) ; i++)
	if(isspace(str[i]))
	    str[i] = ' ';

    //
    // Replace list tags
    //
    const char* li = 
	"\n\\fi-216{\\*\\pn\\pnlvlblt\\pnf4"
	"{\\pntxtb\\'b7}}{\\pntext\\f4\\'b7}";
    const char* lispace = 
	"\n\\fi-216{\\*\\pn\\pnlvlblt\\pnf4"
	"{\\pntxtb\\'b7}}{\\pntext\\f4\\'b7} ";
    str = replace(str, "<ul>", "\001");
    str = replace(str, "</ul>", "\002\n");
    str = replace(str, "<li>", li);
    str = replace(str, "</li>", "");
    while(strstr(str, lispace))
	str = replace(str, lispace, li);

    //
    // Add new paragraphs where necessary
    //
    str = replace(str, "<hr>", "\n\n");
    str = replace(str, "<p>", "\n\n");
    str = replace(str, "</p>", "");
    str = replace(str, "<br>", "\\par");

    //
    // Remove duplicate spaces. This has to be done before replacing
    // HTML attributes with RTF commands that have a space.
    //
    while(strstr(str, "  "))
	str = replace(str, "  ", " ");

    //
    // Replace <em> and </em>
    //
    str = replace(str, "<em>", "\\i ");
    str = replace(str, "</em>", "\\i0 ");

    //
    // Replace <b> and </b>
    //
    str = replace(str, "<b>", "\\b ");
    str = replace(str, "</b>", "\\b0 ");

    //
    // Replace <u> and </u>
    //
    str = replace(str, "<u>", "\\ul ");
    str = replace(str, "</u>", "\\ul0 ");

    //
    // Replace <code> and </code>
    //
    str = replace(str, "<code>", literal_);
    str = replace(str, "</code>", body_);

    //
    // I don't want newlines or spaces at the beginning or end
    //
    for(i = 0 ; i < (CORBA_Long)strlen(str) ; i++)
	if(str[i] != ' ' && str[i] != '\n')
	    break;

    for(j = strlen(str) - 1 ; j >= i ; j--)
	if(str[j] != ' ' && str[j] != '\n')
	    break;

    //
    // Print the paragraph
    //
    for(; i <= j ; i++)
    {
	if(i > 0)
	{
	    if(str[i] == ' ' && str[i - 1] == '\n')
		continue;
	    if(str[i] == ' ' && str[i - 1] == '\001')
		continue;
	    if(str[i] == ' ' && str[i - 1] == '\002')
		continue;
	}

	if(i < (CORBA_Long)strlen(str) - 1)
	{
	    if(str[i] == ' ' && str[i + 1] == '\n')
		continue;
	    if(str[i] == ' ' && str[i + 1] == '\001')
		continue;
	    if(str[i] == ' ' && str[i + 1] == '\002')
		continue;
	}

	if(str[i] == '\001')
	    out.inc();
	else if(str[i] == '\002')
	    out.dec();
	else
	    out << str[i];
    }
}

// ----------------------------------------------------------------------
// Find and replace substrings, case insensitive
// ----------------------------------------------------------------------

char*
IdlRTFGenerator::replace(const char* str, const char* old, const char* repl)
{
    CORBA_String_var result = CORBA_string_alloc(strlen(str));
    result[0] = '\0';

    unsigned int i, j;

    for(i = 0 ; i < strlen(str) ; i++)
    {
	for(j = 0 ; j < strlen(old) ; j++)
	    if(tolower(str[i + j]) != tolower(old[j]))
		break;
	
	if(j == strlen(old))
	{
	    result += repl;
	    i += strlen(old) - 1;
	}
	else
	{
	    result += str[i];
	}
    }
    
    return result._retn();
}

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

void
IdlRTFGenerator::intro(const char* id, const char* name, IdlPrettyPrint& out)
{
    out << '\n' << name;
    out.inc();

    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 << '\n';

	    paragraph(comment, out);

	    break;
	}
    }

    out.dec();
    out << '\n';
}

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

char*
IdlRTFGenerator::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 += '<';
	    result += contentTypeString;
	    
	    if(type -> length())
	    {
		result += ", ";
		result += type -> length();
	    }
	    
	    result += '>';
	    
	    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 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*
IdlRTFGenerator::getAbsolute(CORBA_Contained_ptr contained)
{
    CORBA_ScopedName_var abs = contained -> absolute_name();
    return CORBA_string_dup(abs.in() + 2); // Remove the "::"
}

char*
IdlRTFGenerator::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*
IdlRTFGenerator::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 scope
// ----------------------------------------------------------------------

void
IdlRTFGenerator::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
IdlRTFGenerator::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*
IdlRTFGenerator::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
IdlRTFGenerator::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;
}

