/*
 * Copyright The WildFly Authors
 * SPDX-License-Identifier: Apache-2.0
 */
package org.wildfly.iiop.openjdk.rmi.ir;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.omg.CORBA.ArrayDef;
import org.omg.CORBA.Contained;
import org.omg.CORBA.ContainedHelper;
import org.omg.CORBA.DefinitionKind;
import org.omg.CORBA.FixedDef;
import org.omg.CORBA.IRObject;
import org.omg.CORBA.ORB;
import org.omg.CORBA.PrimitiveDef;
import org.omg.CORBA.Repository;
import org.omg.CORBA.RepositoryOperations;
import org.omg.CORBA.RepositoryPOATie;
import org.omg.CORBA.SequenceDef;
import org.omg.CORBA.StringDef;
import org.omg.CORBA.TypeCode;
import org.omg.CORBA.WstringDef;
import org.omg.PortableServer.POA;
import org.wildfly.iiop.openjdk.logging.IIOPLogger;

/**
 * An Interface Repository.
 *
 * @author <a href="mailto:osh@sparre.dk">Ole Husgaard</a>
 */
class RepositoryImpl extends ContainerImpl implements RepositoryOperations, LocalContainer {

    public RepositoryImpl(ORB orb, POA poa, String name) {
        super(DefinitionKind.dk_Repository, null);

        this.orb = orb;
        this.poa = poa;
        oid = name.getBytes(StandardCharsets.UTF_8);
        oidPrefix = name + ":";
        anonOidPrefix = oidPrefix + "anon";

        repository = this;
    }


    public IRObject getReference() {
        if (ref == null) {
            ref = org.omg.CORBA.RepositoryHelper.narrow(
                    servantToReference(new RepositoryPOATie(this)));
        }
        return ref;
    }

    public void allDone()
            throws IRConstructionException {
        super.allDone();

        // call allDone() for all our sequences
        Iterator iter = sequenceMap.values().iterator();
        while (iter.hasNext())
            ((SequenceDefImpl) iter.next()).allDone();
    }

    public void shutdown() {
        // shutdown all anonymous IR objects in this IR
        for (long i = 1; i < nextPOAId; i++) {
            try {
                getPOA().deactivate_object(getAnonymousObjectId(i));
            } catch (org.omg.CORBA.UserException ex) {
                IIOPLogger.ROOT_LOGGER.warnCouldNotDeactivateAnonIRObject(ex);
            }
        }

        // shutdown this IR's top-level container
        super.shutdown();
    }

    // Repository implementation -------------------------------------

    public Contained lookup_id(String search_id) {
        LocalContained c = _lookup_id(search_id);

        if (c == null)
            return null;

        return ContainedHelper.narrow(c.getReference());
    }

    public TypeCode get_canonical_typecode(TypeCode tc) {
        // TODO
        return null;
    }

    public PrimitiveDef get_primitive(org.omg.CORBA.PrimitiveKind kind) {
        // TODO
        return null;
    }

    public StringDef create_string(int bound) {
        throw IIOPLogger.ROOT_LOGGER.cannotChangeRMIIIOPMapping();
    }

    public WstringDef create_wstring(int bound) {
        throw IIOPLogger.ROOT_LOGGER.cannotChangeRMIIIOPMapping();
    }

    public SequenceDef create_sequence(int bound, org.omg.CORBA.IDLType element_type) {
        throw IIOPLogger.ROOT_LOGGER.cannotChangeRMIIIOPMapping();
    }

    public ArrayDef create_array(int length, org.omg.CORBA.IDLType element_type) {
        throw IIOPLogger.ROOT_LOGGER.cannotChangeRMIIIOPMapping();
    }

    public FixedDef create_fixed(short digits, short scale) {
        throw IIOPLogger.ROOT_LOGGER.cannotChangeRMIIIOPMapping();
    }


    // Y overrides ---------------------------------------------------

    // Package protected ---------------------------------------------

    /**
     * The ORB that I use.
     */
    ORB orb = null;

    /**
     * The POA that I use.
     */
    POA poa = null;

    /**
     * The POA object ID of this repository.
     */
    private byte[] oid = null;

    /**
     * Prefix for POA object IDs of IR objects in this repository.
     */
    private String oidPrefix = null;

    /**
     * Prefix for POA object IDs of "anonymous" IR objects in this repository.
     */
    private String anonOidPrefix = null;

    /**
     * Maps typecodes of sequences defined in this IR to the sequences.
     */
    Map sequenceMap = new HashMap();

    /**
     * Maps repository IDs of sequences defined in this IR to the sequences.
     */
    Map sequenceIdMap = new HashMap();


    LocalContained _lookup_id(String search_id) {
        // mapping of arrays are special
        if (search_id.startsWith("RMI:["))
            return (ValueBoxDefImpl) sequenceIdMap.get(search_id);

        // convert id
        String name = scopedName(search_id);

        // look it up if converted id not null
        //return (name == null) ? null : _lookup(name);
        LocalContained ret = (name == null) ? null : _lookup(name);
        return ret;
    }

    SequenceDefImpl getSequenceImpl(TypeCode typeCode) {
        return (SequenceDefImpl) sequenceMap.get(typeCode);
    }

    void putSequenceImpl(String id, TypeCode typeCode, SequenceDefImpl sequence,
                         ValueBoxDefImpl valueBox) {
        sequenceIdMap.put(id, valueBox);
        sequenceMap.put(typeCode, sequence);
    }

    String getObjectIdPrefix() {
        return oidPrefix;
    }

    // Protected -----------------------------------------------------

    /**
     * Return the POA object ID of this IR object.
     */
    protected byte[] getObjectId() {
        return (byte[]) oid.clone();
    }

    /**
     * Generate the ID of the n-th "anonymous" object created in this IR.
     */
    protected byte[] getAnonymousObjectId(long n) {
        String s = anonOidPrefix + Long.toString(n);
        return s.getBytes(StandardCharsets.UTF_8);
    }

    /**
     * The next "anonymous" POA object ID.
     * While contained IR objects can generate a sensible ID from their
     * repository ID, non-contained objects use this method to get an
     * ID that is unique within the IR.
     */
    protected byte[] getNextObjectId() {
        return getAnonymousObjectId(nextPOAId++);
    }


    // Private -------------------------------------------------------

    /**
     * My CORBA reference.
     */
    private Repository ref = null;

    /**
     * The next "anonymous" POA object ID.
     */
    private long nextPOAId = 1;

    /**
     * Convert a repository ID to an IDL scoped name.
     * Returns <code>null</code> if the ID cannot be understood.
     */
    private String scopedName(String id) {
        if (id == null)
            return null;

        if (id.startsWith("IDL:")) {
            // OMG IDL format

            // Check for base types
            if ("IDL:omg.org/CORBA/Object:1.0".equals(id) ||
                    "IDL:omg.org/CORBA/ValueBase:1.0".equals(id))
                return null;

            // Get 2nd component of ID
            int idx2 = id.indexOf(':', 4); // 2nd colon
            if (idx2 == -1)
                return null; // invalid ID, version part missing
            String base = id.substring(4, id.indexOf(':', 4));

            // Check special prefixes
            if (base.startsWith("omg.org"))
                base = "org/omg" + base.substring(7);
            if (base.startsWith("w3c.org"))
                base = "org/w3c" + base.substring(7);

            // convert '/' to "::"
            StringBuffer b = new StringBuffer();
            for (int i = 0; i < base.length(); ++i) {
                char c = base.charAt(i);

                if (c != '/')
                    b.append(c);
                else
                    b.append("::");
            }

            return b.toString();
        } else if (id.startsWith("RMI:")) {
            // RMI hashed format

            // Get 2nd component of ID
            int idx2 = id.indexOf(':', 4); // 2nd colon
            if (idx2 == -1)
                return null; // invalid ID, version part missing
            String base = id.substring(4, id.indexOf(':', 4));

            // convert '.' to "::"
            StringBuffer b = new StringBuffer();
            for (int i = 0; i < base.length(); ++i) {
                char c = base.charAt(i);

                if (c != '.')
                    b.append(c);
                else
                    b.append("::");
            }

            return b.toString();
        } else
            return null;
    }


    // Inner classes -------------------------------------------------
}
