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

#include <OB/Basic.h>
#include <OB/Except.h>
#include <OB/Template.h>
#include <OB/Declarations.h>
#include <OB/Any.h>
#include <OB/TCKind.h>
#include <OB/TypeCode.h>
#include <OB/IOP.h>
#include <OB/Object.h>
#include <OB/GIOP.h>
#include <OB/IIOP.h>
#include <OB/IntRepMember.h>
#include <OB/ORB.h>
#include <OB/DynAny.h>
#include <OB/DII.h>
#include <OB/Environment.h>
#include <OB/INS.h>

#include <stdlib.h> // For atoi...
#include <ctype.h> // For tolower...

// VC++ has strnicmp instead of strncasecmp
#ifndef HAVE_STRNCASECMP
#   define strncasecmp strnicmp
#endif

//
// Forward declarations.
//
static IOP_IOR* OBIIOPCreateIOR(const char*, CORBA_UShort,
				const OBFixSeq<CORBA_Octet>&,
				const char*);

//
// Exception thrown in the case of an INSURL error.
//
class OBMalformedINSURLException
{
    CORBA_String_var msg_;
    int minor_;

public:
    
    OBMalformedINSURLException(int minor, const char* msg)
	: msg_(msg),
	  minor_(minor)
    {
    }
    
    int
    getMinor() const
    {
	return minor_;
    }

    const char*
    getMessage() const
    {
	return msg_;
    }
};

//
// This class represents an INS address.
//
struct OBINSAddress
{
    CORBA_Octet major;
    CORBA_Octet minor;
    CORBA_String_var host;
    CORBA_UShort port;
};

//
// This class represents an INS URL. This is a URL of the form
// "protocol://addresses/stringified_name".
//
class OBINSURL
{
    //
    // Hide copy constructor and assignment operator.
    //
    OBINSURL(const OBINSURL&);
    void operator=(const OBINSURL&);

    CORBA_String_var protocol_; // The protocol name.

    OBINSAddress* addresses_; // The parsed INS addresses.

    CORBA_ULong numAddrs_; // The number of parsed addresses.

    OBFixSeq<CORBA_Octet> file_; // The parsed file portion of the INS URL.

    //
    // Take a string of the form:
    //
    // <addr_list> = [<address> ,]* <address>
    // <address> = [<version> <host> [: <port>]]
    // <host> = DNS Style Host Name | ip_address
    // <version> = <major> . <minor> @ | empty_string
    // <port> = number
    // <major> = number
    // <minor> = number
    // <string_name>= stringified Name | empty_string
    //
    // and produce an array of OBINSAddress classes.
    //
    void
    parseAddresses(const char* address)
    {
	numAddrs_ = 0;
	
	//
	// Count the number of addresses.
	//
	const char* comma = address;
	do
	{
	    comma = ::strchr(comma, ',');
	    //
	    // If we're not at the end, skip the ',' character.
	    //
	    if(comma != 0)
		++comma;

	    numAddrs_++;
	}
	while(comma != 0);
	
	//
	// Allocate space for the bodies.
	//
	addresses_ = new OBINSAddress[numAddrs_];
	
	//
	// Reset numAddrs_ and comma.
	//
	numAddrs_ = 0;
	comma = address;
	do
	{
	    const char* start = comma;
	    
	    //
	    // Find the next comma.
	    //
	    comma = ::strchr(start, ',');
	    
	    //
	    // This is the end of the address portion. Its either the
	    // end of the string, or the character before the ','.
	    //
	    const char* end = comma;
	    if(end == 0)
		end = start + ::strlen(start);
	    
	    //
	    // If we're not at the end, skip the ',' character.
	    //
	    if(comma != 0)
		++comma;
	    
	    //
	    // Initialize the defaults for the address.
	    //
	    CORBA_String_var host = CORBA_string_dup("localhost");
	    int port = 9999;
	    int major = 1;
	    int minor = 0;
	    
	    const char* addrstart = start;

	    //
	    // Do we have an '@' portion?
	    //
	    const char* at = ::strchr(start, '@');
	    if(at != 0 && at <= end)
	    {
		const char* dot = ::strchr(start, '.');
		if(dot == 0 || dot > end)
		    throw OBMalformedINSURLException(
			OBMinorBadAddress, "Bad version number");

		if(!isdigit(*start) || !isdigit(*(dot+1)))
		    throw OBMalformedINSURLException(
			OBMinorBadAddress, "Bad version number");

		major = ::atoi(start);
		minor = ::atoi(dot + 1);

                //
                // ORBacus 3.x can only create IIOP 1.0 IORs
                //
                if(major != 1 || minor != 0)
		    throw OBMalformedINSURLException(
			OBMinorBadAddress, "Bad version number");

		//
		// The end of the address portion is now the character
		// after the '@'.
		//
		addrstart = at + 1;
	    }
	    
	    //
	    // Find ':' separator.
	    //
	    const char* colon = ::strchr(addrstart, ':');
	    
	    //
	    // No colon? It's only the host. Otherwise both host and
	    // port are specified.
	    //
	    if(colon == 0 || colon > end)
	    {
		if(addrstart != end)
		{
		    host = CORBA_string_alloc(end - addrstart);
		    ::strncpy(host.inout(), addrstart, end - addrstart);
		    host[end - addrstart] = '\0';
		}
	    }
	    else
	    {
		if(colon != addrstart)
		{
		    host = CORBA_string_alloc(colon - addrstart);
		    ::strncpy(host.inout(), addrstart, colon - addrstart);
		    host[colon - addrstart] = '\0';
		}
		port = ::atoi(colon+1);
	    }
	    
	    //
	    // Copy in the address information.
	    //
	    addresses_[numAddrs_].major = major;
	    addresses_[numAddrs_].minor = minor;
	    addresses_[numAddrs_].host = host;
	    addresses_[numAddrs_].port = port;
	    ++numAddrs_;
	}
	while(comma != 0);
    }
    
    inline int
    convertHex(char ch)
    {
	if(ch >= '0' && ch <= '9')
	    return ch - '0';
	if(ch >= 'A' && ch <= 'F')
	    return (ch - 'A') + 10;
	if(ch >= 'a' && ch <= 'f')
	    return (ch - 'a') + 10;
	return -1;
    }

    //
    // Parse the character escapes. A '%' character is used as an
    // escape.  Two hexadecimal characters follow a '%' -- the first
    // is the high order nibble and the second is the low-order
    // nibble. If a '%' is not followed by two hex digits the name is
    // synactically invalid.
    //
    void
    parseFile(const char* file)
    {
	file_.length(0);

	CORBA_ULong len = ::strlen(file);
	const char* end = file + len;
	for(const char* curr = file; curr < end; curr++)
	{
	    CORBA_Octet ch = (CORBA_Octet)*curr;
	    if(ch == '%')
	    {
		if(curr + 2 >= end)
		{
		    throw OBMalformedINSURLException(
			OBMinorBadSchemeSpecificPart, "Bad octet duple");
		}
		char c1 = *++curr;
		char c2 = *++curr;
		ch = (CORBA_Octet)((convertHex(c1) << 4) | convertHex(c2));
	    }
	    file_.append(ch);
	}
    }

public:

    OBINSURL(const char* s)
	: addresses_(0)
    {
	const char* start = ::strstr(s, "://");
	if(start == 0)
	    throw OBMalformedINSURLException(OBMinorOther, "No protocol");

	protocol_ = CORBA_string_alloc(start - s);

	char* p = protocol_.inout();
	while(s != start)
	    *p++ = tolower(*s++);
	*p = '\0';

	//
	// Skip the  "://"
	//
	start += 3;
	const char* end = ::strchr(start, '/');
	if(end == 0)
	    throw OBMalformedINSURLException(OBMinorOther, "No file");

	//
	// Parse the addresses and the file portion of the INS
	// URL.
	//
	CORBA_String_var address = CORBA_string_alloc(end - start);
	::strncpy(address.inout(), start, end - start);
	address[end - start] = '\0';

	parseAddresses(address);
	parseFile(end + 1);
    }

    OBINSURL(const char* protocol, OBINSAddress* addrs, CORBA_ULong numAddrs,
	     const OBFixSeq<CORBA_Octet>& file)
	: protocol_(CORBA_string_dup(protocol)),
	  addresses_(0),
	  numAddrs_(numAddrs),
	  file_(file)
    {
	addresses_ = new OBINSAddress[numAddrs];
	for(CORBA_ULong i = 0; i < numAddrs; i++)
	    addresses_[i] = addrs[i];
    }

    ~OBINSURL()
    {
	delete[] addresses_;
    }

    //
    // Get the protocol.
    //
    const char*
    getProtocol() const
    {
	return protocol_;
    }

    //
    // Get the parsed addresses.
    //
    void
    getAddresses(OBINSAddress*& addrs, CORBA_ULong& num) const
    {
	addrs = addresses_;
	num = numAddrs_;
    }
    
    //
    // Get the file portion.
    //
    const OBFixSeq<CORBA_Octet>&
    getFile() const
    {
	return file_;
    }
};

// ----------------------------------------------------------------------
// OBIIOPNameParser private member implementation
// ----------------------------------------------------------------------

char*
OBIIOPNameParser::next()
{
    CORBA_String_var field = CORBA_string_dup("");
    while(curr_ < path_.length() && valid_)
    {
	char ch = path_[curr_];
	if(ch == '.' || ch == '/')
	{
	    ++curr_;
	    terminator_ = ch;
	    return field._retn();
	}
	
	//
	// Escape character? '.', '/' and '\' are
	// permitted to follow.
	//
	if(ch == '\\')
	{
	    ++curr_;
	    if(curr_ >= path_.length())
	    {
		valid_ = false;
		continue;
	    }
	    
	    ch = path_[curr_];
	    if(ch != '.' && ch != '/' && ch != '\\')
	    {
		valid_ = false;
		continue;
	    }
	}
	field += ch;
	++curr_;
    }
    
    terminator_ = 0;
    return field._retn();
}

bool
OBIIOPNameParser::atEnd() const
{
    return curr_ >= path_.length();
}
    
char
OBIIOPNameParser::terminator() const
{
    return terminator_;
}

void
OBIIOPNameParser::parse()
{
    while(!atEnd() && valid_)
    {
	CORBA_String_var id = next();
	CORBA_String_var kind;
	    
	//
	// If the terminator is a '.' then the next field is the
	// kind.
	//
	if(terminator() == '.')
	{
	    kind = next();
	    //
	    // The kind field is not permitted to end with a '.'.
	    //
	    if(terminator() == '.')
		valid_ = false;
	}
	else
	    kind = CORBA_string_dup("");
	if(valid_)
	{
	    contents_.append(id);
	    contents_.append(kind);
	}
    }

    if(!valid_)
	contents_.length(0);
}
    
// ----------------------------------------------------------------------
// OBIIOPNameParser constructor/destructor
// ----------------------------------------------------------------------

OBIIOPNameParser::OBIIOPNameParser(const OBFixSeq<CORBA_Octet>& path)
    : path_(path),
      curr_(0),
      terminator_(0),
      valid_(true)

{
    parse();
}
    
OBIIOPNameParser::OBIIOPNameParser(const char* path)
    : curr_(0),
      terminator_(0),
      valid_(true)
{
    CORBA_ULong len = ::strlen(path);
    path_.length(len);
    ::memcpy((char*)path_.data(), path, len);
    
    parse();
}

// ----------------------------------------------------------------------
// OBIIOPNameParser public member implementation
// ----------------------------------------------------------------------

bool
OBIIOPNameParser::isValid() const
{
    return valid_;
}

const OBStrSeq&
OBIIOPNameParser::getContents() const
{
    return contents_;
}

// ----------------------------------------------------------------------
// Internal static methods
// ----------------------------------------------------------------------

//
// Do an IIOPLOC resolve.
//
static CORBA_Object_ptr
IIOPLOCToObject(CORBA_ORB_ptr orb, const OBINSURL& url)
{
    OBINSAddress* addrs;
    CORBA_ULong nAddr;

    url.getAddresses(addrs, nAddr);

    const OBFixSeq<CORBA_Octet>& key = url.getFile();

    //
    // Although an empty object-key is allowed by the INS specification
    // we're going to make this an invalid IOR.
    //
    if(key.length() == 0)
        throw CORBA_BAD_PARAM("", OBMinorOther, CORBA_COMPLETED_NO);

    //
    // We only use the first address in the list; all others are ignored
    //
    IOP_IOR_var ior = OBIIOPCreateIOR(addrs[0].host, addrs[0].port, key, "");
    return orb -> _OB_createObject(ior);
}

//
// Do an IIOPNAME resolve.
//
static CORBA_Object_ptr
IIOPNAMEToObject(CORBA_ORB_ptr orb, const OBINSURL& url)
{
    //
    // Object-key of the root naming context. See 98-10-11 3-5 for
    // details. Unfortunately there is a ambiguity in the
    // specification.  On pg. 4-20 the key is identified as
    // "NamingService". Since "NameService" is used first, and
    // more than once in the specification we use this.
    //
    const char nameKey[] = "NameService";

    //
    // Create the object-key from the string.
    //
    OBFixSeq<CORBA_Octet> key;
    key.length(sizeof nameKey-1);
    ::memcpy((char*)key.data(), nameKey, sizeof nameKey -1);

    OBINSAddress* addr;
    CORBA_ULong nAddr;
	
    url.getAddresses(addr, nAddr);

    //
    // Do an IIOPLOC resolve of this key.
    //
    OBINSURL nUrl("iioploc", addr, nAddr, key);
	
    CORBA_Object_var service = IIOPLOCToObject(orb, nUrl);

    const OBFixSeq<CORBA_Octet>& file = url.getFile();
    
    //
    // If we don't have a specification then we return the naming
    // context as specified on 4-18:
    //
    // iiopname:///
    //
    // This URL repesents the naming context returned by the agent
    // running on the local host at the default port. It is
    // equivalent to iioploc:///NameService.
    //
    if(file.length() == 0)
	return service._retn();
    
    //
    // TODO: check this code for memory leaks.
    //
    try
    {
	//
	// Create typecodes for Name and NameComponent
	//
	CORBA_StructMemberSeq contents;
	contents.length(2);
	contents[0].name = CORBA_string_dup("id");
	contents[0].type = orb -> create_string_tc(0);
	contents[1].name = CORBA_string_dup("kind");
	contents[1].type = orb -> create_string_tc(0);
	CORBA_TypeCode_var tcNameComponent =
	    orb -> create_struct_tc("IDL:omg.org/CosNaming/NameComponent:1.0",
				    "NameComponent", contents);

	CORBA_TypeCode_var tcName =
	    orb -> create_sequence_tc(0, tcNameComponent);

	//
	// Parse path, create NameComponent sequence.
	//
	OBIIOPNameParser parser(file);
	if(!parser.isValid())
	    throw CORBA_BAD_PARAM("", OBMinorBadSchemeSpecificPart,
				  CORBA_COMPLETED_NO);

	const OBStrSeq& content = parser.getContents();
	assert(content.length() % 2 == 0);

	CORBA_AnySeq as;
	for(CORBA_ULong i = 0; i < content.length(); i += 2)
	{
	    //
	    // Create the DynStruct containing the id and kind fields.
	    //
	    CORBA_DynStruct_var name =
		orb -> create_dyn_struct(tcNameComponent);

	    name -> insert_string(content[i]);
	    name -> insert_string(content[i + 1]);

	    CORBA_Any_var nany = name -> to_any();

	    name -> destroy();

	    as.append(nany);
	}

	//
	// Create the DynSequence representing the name.
	//
	CORBA_DynSequence_var seq = orb -> create_dyn_sequence(tcName);
	seq -> length(as.length());
	seq -> set_elements(as);

	CORBA_Any_var any = seq -> to_any();
	seq -> destroy();

	//
	// Create the DII request to the naming service to resolve the
	// name.
	//
	CORBA_Request_var request = service -> _request("resolve");

	//
	// Copy in the arguments.
	//
	CORBA_Any& arg = request -> add_in_arg();
	arg = *any;
	request -> set_return_type(CORBA__tc_Object);

        try
        {
            //
            // Invoke the request.
            //
            request -> invoke();

            //
            // Return the result if there was no exception
            //
            if(request -> env() -> exception() == 0)
            {
                CORBA_Object_ptr obj;
                request -> return_value() >>= obj;

                return CORBA_Object::_duplicate(obj);
            }
        }
        catch(const CORBA_SystemException&)
        {
            // Fall through
        }

        //
        // An exception must have occurred
        //
        throw CORBA_BAD_PARAM("", OBMinorOther, CORBA_COMPLETED_NO);
    }
    //
    // All of the following exceptions denote some sort of
    // internal error.
    //
    catch(const CORBA_UserException&)
    {
	assert(false);
    }
    return CORBA_Object::_nil(); // Some compilers need this
}

//
// Convert an IOR: IOP object-reference to an Object.
//
static CORBA_Object_ptr
IORStringToObject(CORBA_ORB_ptr orb, const char* s)
{
    if(::strlen(s) % 2)
	throw CORBA_BAD_PARAM("", OBMinorBadSchemeSpecificPart,
			      CORBA_COMPLETED_NO);
    
    CORBA_Octet* const buf = OBAsciiToOctets(s);
    const CORBA_Octet* oct = buf;

    CORBA_Boolean endian;
    OBUnmarshal(endian, oct, false);
    
    IOP_IOR_var ior = new IOP_IOR;
    OBUnmarshal(ior.inout(), oct, endian != OBEndian);
    
    delete [] buf;

    return orb -> _OB_createObject(ior);
}

//
// Convert an iiop:// object-URL to an Object.
//
static CORBA_Object_ptr
IIOPStringToObject(CORBA_ORB_ptr orb, const char* s)
{
    //
    // The string passed is without the iiop: portion of the protocol.
    //
    const char* start = ::strstr(s, "//");
    if(start == 0)
	throw CORBA_BAD_PARAM("", OBMinorOther, CORBA_COMPLETED_NO);
    start += 2;

    //
    // Find the ':' separating the host and the port.
    //
    const char* end = ::strchr(start, ':');
    if(end == 0)
	throw CORBA_BAD_PARAM("", OBMinorOther, CORBA_COMPLETED_NO);
    CORBA_String_var host = CORBA_string_alloc(end - start);
    ::strncpy(host.inout(), start, end - start);
    host[end - start] = '\0';

    //
    // Find the '/' separating the address and the file.
    //
    start = end + 1;
    end = ::strchr(start, '/');
    if(end == 0)
	throw CORBA_BAD_PARAM("", OBMinorOther, CORBA_COMPLETED_NO);

    CORBA_UShort port = ::atoi(start);

    //
    // Find the end of the name. If the string terminates in a newline
    // then eat it.
    //
    start = end + 1;
    end = ::strchr(start, '\n');
    if(end != 0)
	--end;
    else
	end = start + ::strlen(start);
    
    CORBA_String_var name = CORBA_string_alloc(end - start);
    ::strncpy(name.inout(), start, end - start);
    name[end - start] = '\0';

    return orb -> get_inet_object(host, port, name);
}

// ----------------------------------------------------------------------
// ORB public member functions
// ----------------------------------------------------------------------

CORBA_Object_ptr
CORBA_ORB::string_to_object(const char* s)
{
    //
    // Various IOR prefixes.
    //
    static const char iorPrefix[] = "ior:";
    static const char iiopPrefix[] = "iiop:";
    static const char iiopnamePrefix[] = "iiopname";
    static const char iioplocPrefix[] = "iioploc";

    assert_nca(s, OBNCANullString);

    size_t len = ::strlen(s);

    if(len > sizeof iorPrefix &&
       strncasecmp(s, iorPrefix, sizeof iorPrefix - 1) == 0)
	return IORStringToObject(this, s + sizeof iorPrefix - 1);

    if(len > sizeof iiopPrefix &&
       strncasecmp(s, iiopPrefix, sizeof iiopPrefix - 1) == 0)
	return IIOPStringToObject(this, s + sizeof iiopPrefix - 1);
    
    try
    {
	OBINSURL url(s);

	if(::strcmp(url.getProtocol(), iiopnamePrefix) == 0)
	    return IIOPNAMEToObject(this, url);

	if(::strcmp(url.getProtocol(), iioplocPrefix) == 0)
	    return IIOPLOCToObject(this, url);
    }
    catch(const OBMalformedINSURLException& e)
    {
	throw CORBA_BAD_PARAM("", e.getMinor(), CORBA_COMPLETED_NO);
    }
	  
    throw CORBA_BAD_PARAM("", OBMinorBadSchemeName, CORBA_COMPLETED_NO);

    return CORBA_Object::_nil(); // Some compilers need this
}

CORBA_Object_ptr
CORBA_ORB::get_inet_object(const char* host, CORBA_UShort port,
			   const char* name, const char* id)
{
    //
    // Makeup object key.
    //
    // I don't use string marshal functions here because I
    // don't want to have a string length encoded. That would
    // cause byte order dependencies.
    //
    OBFixSeq<CORBA_Octet> key;
    key.length(::strlen(name)+1);
    ::strcpy((char*)key.data(), name);

    IOP_IOR_var ior = OBIIOPCreateIOR(host, port, key, id);
    return _OB_createObject(ior);
}

// ----------------------------------------------------------------------
// Some utility functions
// ----------------------------------------------------------------------

static IOP_IOR*
OBIIOPCreateIOR(const char* host, CORBA_UShort port,
		const OBFixSeq<CORBA_Octet>& key, const char* id)
{
    //
    // Create a new profile
    //
    IIOP_ProfileBody body;
    body.iiop_version.major = 1;
    body.iiop_version.minor = 0;
    body.host = host;
    body.port = port;

    body.object_key.length(key.length());
    memcpy(body.object_key.data(), key.data(), key.length());

    //
    // Create new IOR with the new profile
    //
    IOP_IOR_var ior = new IOP_IOR;
    ior -> type_id = id;
    ior -> profiles.length(1);
    ior -> profiles[0].tag = IOP_TAG_INTERNET_IOP;
    CORBA_ULong count = 0;
    OBMarshalCount(OBEndian, count);
    OBMarshalCount(body, count);
    ior -> profiles[0].profile_data.length(count);
    CORBA_Octet* oct = ior -> profiles[0].profile_data.data();
#ifdef OB_CLEAR_MEM
    memset(oct, 0, count);
#endif
    OBMarshal(OBEndian, oct);
    OBMarshal(body, oct);

    return ior._retn();
}
