/*
 *  VisitorAdapter.java v0.10 20-DEC-1999
 *  Copyright (c) TKK/TLM/Calypso
 *  Author: Alexey Mednonogov
 */

package codec.adapt;

import java.io.*;
import java.util.*;

import codec.*;
import codec.adapt.*;
import codec.convert.*;
import codec.debug.*;
import codec.dyntree.*;
import codec.export.*;
import codec.orb.*;
import codec.pco.*;
import codec.server.*;
import codec.client.*;
import codec.visit.*;
import codec.build.*;

/** Class performing adaptation of CORBA NVList introspection capabilities
 *  to requirements of e.g. ServerCallVisitor. Ordered collection of objects
 *  is created so that it can be directly employed by visit() methods.
 *  Resulting collection can be accessed as DynIterator. */
final public class VisitorAdapter extends CorbaTtcnAdapter {

   	private Vector vector;
   	private org.omg.CORBA.ORB orb;
   	private DynDependencyManager relations;

   	// Have we just encapsulated DynUnion into DynStruct or not yet.
   	// Influences behaviour of "_tk_union" branch of "introspect()":
   	private boolean isUnionEncapsulated;

	private org.omg.CORBA.DynUnion dynUnion_;

   	/** Perform adaptation for Server Call PDU. */
   	public static final int OPTIONS_SERVER_CALL  = 0;
    /** Perform adaptation for Client Reply PDU. */
   	public static final int OPTIONS_CLIENT_REPLY = 1;

	public DynIterator iterator() {
		return new DynIterator(vector.iterator());
	}

   	/** Introspect unnamed components of DynArray. */
   	private void introspectArray(org.omg.CORBA.Any origin,
	    org.omg.CORBA.DynArray dynArray) {

		if (dynArray.seek(0) == false) {

			String kind = "";
			try {
				kind = Debug.tcKindToString(
					AnyGeneric.removeAliases(
						dynArray.to_any().type()).kind());
			}
			catch (org.omg.CORBA.DynAnyPackage.Invalid ex) { }

			System.err.println("VisitorAdapter::introspectArray(): " +
                "An array has been detected that contains no fields (" +
				kind + ").");
			System.exit(0);
		}

		relations.setContentDependency(origin, dynArray);
        org.omg.CORBA.DynAny component = null;
		dynArray.rewind();

		do {
		   	org.omg.CORBA.Any any = orb.create_any();
			component = dynArray.current_component();
		   	any.type(component.type());

			relations.setContentDependency(component, any);
		   	relations.setContainerDependency(dynArray, component);

		   	introspect(new org.omg.CORBA.NameValuePair("", any));

		} while (dynArray.next());
	}

	/** Introspect named components of DynStruct. */
    private void introspectStruct(org.omg.CORBA.Any origin,
	    org.omg.CORBA.DynStruct dynStruct) {

		if (dynStruct.seek(0) == false) {

			String kind = "";
			try {
				kind = Debug.tcKindToString(
					AnyGeneric.removeAliases(
						dynStruct.to_any().type()).kind());
			}
			catch (org.omg.CORBA.DynAnyPackage.Invalid ex) { }

			System.err.println("VisitorAdapter::introspectStruct(): " +
                "A structure has been detected that contains no fields (" +
				kind + ").");
			System.exit(0);
		}

		relations.setContentDependency(origin, dynStruct);
        org.omg.CORBA.DynAny component = null;
		dynStruct.rewind();

		do {
		   	org.omg.CORBA.Any any = orb.create_any();
			component = dynStruct.current_component();
		   	any.type(component.type());

			relations.setContentDependency(component, any);
		   	relations.setContainerDependency(dynStruct, component);

		   	introspect(new org.omg.CORBA.NameValuePair(
				dynStruct.current_member_name(), any));

		} while (dynStruct.next());
	}

   	private void introspect(org.omg.CORBA.NameValuePair pair) {

		int i;

		org.omg.CORBA.DynAny dynAny = null;
		org.omg.CORBA.Any any = null;
		DynNode dynNode = null;

		org.omg.CORBA.TypeCode code = pair.value.type();
		code = AnyGeneric.removeAliases(code);

        switch (code.kind().value()) {

        case org.omg.CORBA.TCKind._tk_boolean:
        case org.omg.CORBA.TCKind._tk_char:
        case org.omg.CORBA.TCKind._tk_long:
        case org.omg.CORBA.TCKind._tk_longlong:
        case org.omg.CORBA.TCKind._tk_octet:
        case org.omg.CORBA.TCKind._tk_short:
        case org.omg.CORBA.TCKind._tk_string:
        case org.omg.CORBA.TCKind._tk_ulong:
        case org.omg.CORBA.TCKind._tk_ulonglong:
        case org.omg.CORBA.TCKind._tk_ushort:

			vector.add(new DynNode(
				pair.id, pair.value, DynNode.FLAGS_LEAF));
            break;

   		case org.omg.CORBA.TCKind._tk_array:
			
			vector.add(new DynNode(
				pair.id, pair.value, DynNode.FLAGS_BEG_OF_NODE));

			org.omg.CORBA.DynArray dynArray = null;
			try {
				dynArray = orb.create_dyn_array(pair.value.type());
			}
   			catch (org.omg.CORBA.ORBPackage.InconsistentTypeCode itc) {
	   			System.err.println("VisitorAdapter::introspect(): " +
                    "Internal error -- CORBA exception (_tk_array).");
				System.exit(0);
			}

			if (dynArray == null) {
				System.err.println("VisitorAdapter::introspect(): " +
                    "Internal error -- null pointer (_tk_array).");
				System.exit(0);
			}
			introspectArray(pair.value, dynArray);

			vector.add(new DynNode(
				pair.id, pair.value, DynNode.FLAGS_END_OF_NODE));
            break;

        case org.omg.CORBA.TCKind._tk_enum:
			// Handling "enum" is somehow complicated since OpenTTCN seem
			// to support it as a pure INTEGER while CORBA treats it as
			// structured type. To provide compatibility with Visitor,
			// "enum" value is treated as a primitive type, i.e. it will be
			// iterated as a single DynNode with "flags" set to "FLAGS_LEAF",
			// burdening the Visitor with creating "DynEnum" and handling it
			// by itself:
            
			vector.add(new DynNode(
				pair.id, pair.value, DynNode.FLAGS_LEAF));
            break;

   		case org.omg.CORBA.TCKind._tk_sequence:

			// Most of sequence introspection will be done later, once
			// Visitor finds out how many elements sequence actually has.
			// Then the iterator will be updated accordingly by calling
			// method updateSequence():

			vector.add(new DynNode(
				pair.id, pair.value, DynNode.FLAGS_BEG_OF_NODE));

			vector.add(new DynNode(
				pair.id, pair.value, DynNode.FLAGS_LEAF));

			vector.add(new DynNode(
				pair.id, pair.value, DynNode.FLAGS_END_OF_NODE));
            break;

		case org.omg.CORBA.TCKind._tk_struct:
		case org.omg.CORBA.TCKind._tk_except:
					
			vector.add(new DynNode(
				pair.id, pair.value, DynNode.FLAGS_BEG_OF_NODE));

			org.omg.CORBA.DynStruct dynStruct = null;
			try {
				dynStruct = orb.create_dyn_struct(pair.value.type());
			}
   			catch (org.omg.CORBA.ORBPackage.InconsistentTypeCode itc) {
	   			System.err.println("VisitorAdapter::introspect(): " +
                    "Internal error -- CORBA exception (_tk_struct).");
				System.exit(0);
			}

			if (dynStruct == null) {
				System.err.println("VisitorAdapter::introspect(): " +
                    "Internal error -- null pointer (_tk_struct).");
				System.exit(0);
			}
			introspectStruct(pair.value, dynStruct);

			vector.add(new DynNode(
				pair.id, pair.value, DynNode.FLAGS_END_OF_NODE));
			
            break;

		case org.omg.CORBA.TCKind._tk_union:

			if (isUnionEncapsulated == false) {

				// Before handling "union", we encapsulate it into "struct"
				// containing discriminator according to specification:

				// Save DynUnion to (global) class field:
				dynUnion_ = null;
				try {
					dynUnion_ = orb.create_dyn_union(pair.value.type());
				}
				catch (org.omg.CORBA.ORBPackage.InconsistentTypeCode itc) {
					System.err.println("VisitorAdapter::introspect(): " +
                        "Internal error -- InconsistentTypeCode (_tk_union).");
					System.exit(0);
				}

				if (dynUnion_ == null) {
					System.err.println("VisitorAdapter::introspect(): " +
                        "Internal error -- null pointer (_tk_union).");
					System.exit(0);
				}
				relations.setContentDependency(pair.value, dynUnion_);

				// Create instance of "UnionStruct":

				org.omg.CORBA.TypeCode tc = UnionStructHelper.type(
					dynUnion_.discriminator().type(), dynUnion_.type());

				org.omg.CORBA.Any anyStruct = orb.create_any();
				anyStruct.type(tc);

				isUnionEncapsulated = true;
				introspect(new org.omg.CORBA.NameValuePair(
					pair.id, anyStruct));
			}
			else {
				// Here we are on deeper level of introspection. "Union" is
				// already encapsulated into "struct":
				
				vector.add(new DynNode(
					pair.id, pair.value, DynNode.FLAGS_BEG_OF_NODE));
				vector.add(new DynNode(
					pair.id, pair.value, DynNode.FLAGS_LEAF));
				vector.add(new DynNode(
					pair.id, pair.value, DynNode.FLAGS_END_OF_NODE));
				relations.setContentDependency(dynUnion_, pair.value);
				isUnionEncapsulated = false;
			}
            break;

        case org.omg.CORBA.TCKind._tk_void:
			// If return value of operation is of type "void", then it is
			// skipped in the corresponding PDU. If anything  other than
            // "RET_value" is here, such situation is treated as error:
			if (pair.id.equals("RET_value") == false) {
				System.err.println("VisitorAdapter::introspect(): " +
                    "Internal error -- only \"RET_value\" is allowed " +
                    "to be of type \"void\".");
				System.exit(0);
			}
			break;

        default:
            System.err.println("VisitorAdapter::introspect(): " +
                "Data type not supported (\"" + pair.id + "\" : " + 
				Debug.tcKindToString(code.kind()) + ").");
            System.exit(0);
		}
	}

   	/** Create OpenTTCN-compatible model of structured data from input.
   	 *  Parameter "result" shall be set to "null" in case "options" are set
   	 *  to value different from OPTIONS_CLIENT_REPLY, otherwise it shall
   	 *  point to the result of the operation. */
   	public void create(org.omg.CORBA.NVList nvList,
		org.omg.CORBA.NamedValue result, int options) {

		vector = new Vector(100, 100);
		relations = new DynDependencyManager();
		orb = CorbaServer.getCodecObject().getOrbGeneric().getORB();
		isUnionEncapsulated = false;

		if (options == OPTIONS_CLIENT_REPLY) {

			introspect(new org.omg.CORBA.NameValuePair(
				"RET_value", result.value()));
		}

		for (int i = 0; i < nvList.count(); i++) {

   			org.omg.CORBA.NamedValue namedValue = null;
			try {
                namedValue = nvList.item(i);
            }
            catch (org.omg.CORBA.Bounds ex) {

                System.err.println("VisitorAdapter::create(): " +
					"Index out of bounds.");
                System.exit(0);
            }

			switch (options) {

   			case OPTIONS_SERVER_CALL:
				if ((namedValue.flags() != org.omg.CORBA.ARG_IN.value) &&
					(namedValue.flags() != org.omg.CORBA.ARG_INOUT.value)) {
					continue;
				}
				break;

			case OPTIONS_CLIENT_REPLY:
				if ((namedValue.flags() != org.omg.CORBA.ARG_OUT.value) &&
					(namedValue.flags() != org.omg.CORBA.ARG_INOUT.value)) {
					continue;
				}
				break;

   			default:
				System.err.println("VisitorAdapter::create(): " +
					"Internal error.");
                System.exit(0);
			}

			introspect(new org.omg.CORBA.NameValuePair(
				namedValue.name(), namedValue.value()));
		}
	}

   	/** Update contents of iterator (internally backed by "vector") as soon
     *  as it becomes clear which branch of "union" has been chosen. */
	public void updateUnion(DynIterator dynIterator,
        org.omg.CORBA.Any discriminator) {

		// At this point Visitor has already inspected part of the PDU,
		// ending at node "visitChoiceBegin()":
		if (dynIterator.hasNext() == false) {

			System.err.println("VisitorAdapter::updateUnion(): " + 
				"Internal error.");
            System.exit(0);
		}

		// Get next "dynNode" and check that it contains "union" (that
		// didn't go through normal introspection):
		DynNode dynNode = dynIterator.next();

		if ((dynNode.flags() != DynNode.FLAGS_LEAF) ||

			(AnyGeneric.removeAliases(dynNode.value().type()).kind().value()
             != org.omg.CORBA.TCKind._tk_union)) {

			System.err.println("VisitorAdapter::updateUnion(): " + 
                "Internal error.");
            System.exit(0);
		}

		vector = new Vector(100, 100);

		org.omg.CORBA.DynUnion dynUnion = null;
		try {
		   	dynUnion = orb.create_dyn_union(dynNode.value().type());
		}
	   	catch (org.omg.CORBA.ORBPackage.InconsistentTypeCode itc) {
			System.err.println("VisitorAdapter::updateUnion(): " +
                "Internal error -- InconsistentTypeCode.");
			System.exit(0);
		}

		if (dynUnion == null) {
			System.err.println("VisitorAdapter::updateUnion(): " +
                "Internal error -- null pointer.");
			System.exit(0);
	    }
		relations.setContentDependency(dynNode.value(), dynUnion);

		org.omg.CORBA.DynAny dynDiscr = dynUnion.discriminator();
		org.omg.CORBA.DynAny dynMember = null;
		org.omg.CORBA.Any anyMember = null;
		String memberName = null;

		try {
			dynDiscr.from_any(discriminator);
			dynMember = dynUnion.member();
			anyMember = orb.create_any();
			anyMember.type(dynMember.type());
		    memberName = dynUnion.member_name();
		}
		catch(org.omg.CORBA.DynAnyPackage.Invalid ex) {

			System.err.println("VisitorAdapter::updateUnion(): " +
                "Internal error.");
            System.exit(0);
		}
		relations.setContainerDependency(dynUnion, dynMember);
		relations.setContentDependency(dynMember, anyMember);

        introspect(new org.omg.CORBA.NameValuePair(memberName, anyMember));

        if (dynIterator.hasNext() == false) {

			System.err.println("VisitorAdapter::updateUnion(): " + 
                "Internal error.");
            System.exit(0);
		}

		dynNode = dynIterator.next();

		if ((dynNode.flags() != DynNode.FLAGS_END_OF_NODE) ||

			(AnyGeneric.removeAliases(dynNode.value().type()).kind().value()
             != org.omg.CORBA.TCKind._tk_union)) {

			System.err.println("VisitorAdapter::updateUnion(): " + 
                "Internal error.");
            System.exit(0);
		}

		vector.add(dynNode);

		// Copy the tail of the iterator to vector:
		while (dynIterator.hasNext()) vector.add(dynIterator.next());
    }

	/** Update contents of iterator (internally backed by "vector") as soon
     *  as it becomes clear how many items of data "sequence" actually has. */
	public void updateSequence(DynIterator dynIterator, int length) {

		// At this point Visitor has already inspected part of the PDU,
		// ending at node "visitSequenceOfBegin()":
		if (dynIterator.hasNext() == false) {

			System.err.println("VisitorAdapter::updateSequence(): " + 
				"Internal error.");
            System.exit(0);
		}

		// Get next "dynNode" and check that it contains "sequence" (that
		// didn't go through normal introspection):
		DynNode dynNode = dynIterator.next();

		if ((dynNode.flags() != DynNode.FLAGS_LEAF) ||

			(AnyGeneric.removeAliases(dynNode.value().type()).kind().value()
             != org.omg.CORBA.TCKind._tk_sequence)) {

			System.err.println("VisitorAdapter::updateSequence(): " + 
                "Internal error.");
            System.exit(0);
		}

		vector = new Vector(100, 100);

		org.omg.CORBA.DynSequence dynSeq = null;
		try {
		   	dynSeq = orb.create_dyn_sequence(dynNode.value().type());
		}
	   	catch (org.omg.CORBA.ORBPackage.InconsistentTypeCode itc) {
			System.err.println("VisitorAdapter::updateSequence(): " +
                "Internal error -- InconsistentTypeCode.");
			System.exit(0);
		}

		if (dynSeq == null) {
			System.err.println("VisitorAdapter::updateSequence(): " +
                "Internal error -- null pointer.");
			System.exit(0);
	    }
		relations.setContentDependency(dynNode.value(), dynSeq);
		dynSeq.length(length);

		if (dynSeq.seek(0) == true) {

			org.omg.CORBA.DynAny component = null;
			dynSeq.rewind();

			do {
				org.omg.CORBA.Any any = orb.create_any();
				component = dynSeq.current_component();
				any.type(component.type());

				relations.setContentDependency(component, any);
				relations.setContainerDependency(dynSeq, component);

				introspect(new org.omg.CORBA.NameValuePair("", any));

			} while (dynSeq.next());
		}

        if (dynIterator.hasNext() == false) {

			System.err.println("VisitorAdapter::updateSequence(): " + 
                "Internal error.");
            System.exit(0);
		}

		dynNode = dynIterator.next();

		if ((dynNode.flags() != DynNode.FLAGS_END_OF_NODE) ||

			(AnyGeneric.removeAliases(dynNode.value().type()).kind().value()
             != org.omg.CORBA.TCKind._tk_sequence)) {

			System.err.println("VisitorAdapter::updateSequence(): " + 
                "Internal error.");
            System.exit(0);
		}

		vector.add(dynNode);

		// Copy the tail of the iterator to vector:
		while (dynIterator.hasNext()) vector.add(dynIterator.next());
    }

   	/** Update contents of NVList so that it points to actual data. */
   	public void updateContents() {

		relations.updateContents();
	}
}
