package org.jacorb.notification;

/*
 *        JacORB - a free Java ORB
 *
 *   Copyright (C) 1999-2014 Gerald Brose / The JacORB Team.
 *
 *   This library is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU Library General Public
 *   License as published by the Free Software Foundation; either
 *   version 2 of the License, or (at your option) any later version.
 *
 *   This library is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *   Library General Public License for more details.
 *
 *   You should have received a copy of the GNU Library General Public
 *   License along with this library; if not, write to the Free
 *   Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

import java.util.Iterator;
import java.util.List;

import org.jacorb.notification.filter.ComponentName;
import org.jacorb.notification.filter.EvaluationContext;
import org.jacorb.notification.filter.EvaluationException;
import org.jacorb.notification.filter.EvaluationResult;
import org.jacorb.notification.filter.RuntimeVariable;
import org.jacorb.notification.interfaces.Disposable;
import org.jacorb.notification.interfaces.FilterStage;
import org.jacorb.notification.interfaces.Message;
import org.jacorb.notification.util.AbstractPoolable;
import org.omg.CORBA.Any;
import org.omg.CORBA.AnyHolder;
import org.omg.CosNotification.Property;
import org.omg.CosNotification.StructuredEvent;
import org.omg.CosNotifyFilter.Filter;
import org.omg.CosNotifyFilter.MappingFilter;
import org.omg.CosNotifyFilter.UnsupportedFilterableData;

/**
 * @author Alphonse Bendt
 */

public abstract class AbstractMessage extends AbstractPoolable
{
    /**
     * Instead of directly using an instance of AbstractMessage an indirection via MessageHandle is
     * used. This way the Data in AbstractMessage is kept only once. MessageHandle uses reference
     * counting. As the last MessageHandle is disposed the AbstractMessage containing the data can
     * be disposed. MessageHandle also allows the priority and timeout to be changed on a per handle
     * base (the underlying AbstractMessage isn't affected by this)
     */
    class MessageHandle implements Message, Disposable
    {
        /**
         * Listener that gets notified if the State of this MessageHandle changes.
         */
        private MessageStateListener eventStateListener_;

        private boolean isInvalid_ = false;

        /**
         * flag to indicate that the Priority has been changed for this Messagehandle.
         */
        private boolean isPriorityOverridden = false;

        /**
         * if isPriorityOverridden_ is true priority_ contains the Priority for this MessageHandle.
         */
        private int priority_;

        /**
         * flag to indicate that the Timeout has been changed for this Messagehandle.
         */
        private boolean isTimeoutOverridden_ = false;

        /**
         * if isTimeoutOverridden_ is true timeOut_ contains the Timeout for this MessageHandle.
         */
        private long timeOut_;

        ////////////////////

        /**
         * default ctor. adds a reference to the underlying message.
         */
        MessageHandle()
        {
            addReference();
        }

        /**
         * copy ctor. adds a reference to the underlying message.
         */
        private MessageHandle(int priority, boolean priorityOverride, long timeout,
                boolean timeoutOverride)
        {
            // i would like to write this() here to call the no-args
            // constructor.
            // this compiles but results in java.lang.VerifyErrors
            // at runtime
            addReference();

            priority_ = priority;
            isPriorityOverridden = priorityOverride;
            timeOut_ = timeout;
            isTimeoutOverridden_ = timeoutOverride;
        }

        /**
         * set the Inital FilterStage (the ProxyConsumer that has received the Message).
         */
        public void setInitialFilterStage(FilterStage s)
        {
            AbstractMessage.this.setFilterStage(s);
        }

        public FilterStage getInitialFilterStage()
        {
            return AbstractMessage.this.getFilterStage();
        }

        public String getConstraintKey()
        {
            return AbstractMessage.this.getConstraintKey();
        }

        public Any toAny()
        {
            return AbstractMessage.this.toAny();
        }

        public Property[] toTypedEvent() throws NoTranslationException
        {
            return AbstractMessage.this.toTypedEvent();
        }

        public StructuredEvent toStructuredEvent()
        {
            return AbstractMessage.this.toStructuredEvent();
        }

        public int getType()
        {
            return AbstractMessage.this.getType();
        }

        public EvaluationResult extractValue(EvaluationContext context,
                ComponentName componentName, RuntimeVariable runtimeVariable)
                throws EvaluationException
        {
            return AbstractMessage.this.extractValue(context, componentName, runtimeVariable);
        }

        public EvaluationResult extractValue(EvaluationContext context, ComponentName componentName)
                throws EvaluationException
        {
            return AbstractMessage.this.extractValue(context, componentName);
        }

        public EvaluationResult extractFilterableData(EvaluationContext context,
                ComponentName componentRootNode, String variable) throws EvaluationException
        {
            return AbstractMessage.this.extractFilterableData(context, componentRootNode, variable);
        }

        public EvaluationResult extractVariableHeader(EvaluationContext context,
                ComponentName componentName, String s) throws EvaluationException
        {
            return AbstractMessage.this.extractVariableHeader(context, componentName, s);
        }

        public boolean hasStartTime()
        {
            return AbstractMessage.this.hasStartTime();
        }

        public long getStartTime()
        {
            return AbstractMessage.this.getStartTime();
        }

        public boolean hasStopTime()
        {
            return AbstractMessage.this.hasStopTime();
        }

        public long getStopTime()
        {
            return AbstractMessage.this.getStopTime();
        }

        public boolean hasTimeout()
        {
            return isTimeoutOverridden_ || AbstractMessage.this.hasTimeout();
        }

        public long getTimeout()
        {
            if (isTimeoutOverridden_)
            {
                return timeOut_;
            }
            return AbstractMessage.this.getTimeout();
        }

        public void setTimeout(long timeout)
        {
            timeOut_ = timeout;

            isTimeoutOverridden_ = true;

            if (eventStateListener_ != null)
            {
                eventStateListener_.actionLifetimeChanged(timeout);
            }
        }

        public void setPriority(int priority)
        {
            isPriorityOverridden = true;

            priority_ = priority;
        }

        public int getPriority()
        {
            if (isPriorityOverridden)
            {
                return priority_;
            }
            return AbstractMessage.this.getPriority();
        }

        public boolean match(FilterStage s)
        {
            return AbstractMessage.this.match(s);
        }

        public boolean match(MappingFilter m, AnyHolder r) throws UnsupportedFilterableData
        {
            return AbstractMessage.this.match(m, r);
        }

        public Object clone()
        {
            try
            {
                checkInvalid();

                return new MessageHandle(priority_, isPriorityOverridden, timeOut_, isTimeoutOverridden_);
            } catch (IllegalArgumentException e)
            {
                return null;
            }
        }

        public void dispose()
        {
            removeReference();
        }

        public synchronized boolean isInvalid()
        {
            return isInvalid_;
        }

        public void setMessageStateListener(MessageStateListener listener)
        {
            eventStateListener_ = listener;
        }

        public MessageStateListener removeMessageStateListener()
        {
            MessageStateListener _listener = eventStateListener_;
            eventStateListener_ = null;

            return _listener;
        }

        public synchronized void actionTimeout()
        {
            isInvalid_ = true;
        }

        public long getReceiveTimestamp()
        {
            return AbstractMessage.this.getReceiveTimestamp();
        }

        public String toString()
        {
            return "[Message/" + AbstractMessage.this + "]";
        }

        private void checkInvalid() throws IllegalArgumentException
        {
            if (isInvalid())
            {
                throw new IllegalArgumentException("This Notification has been invalidated");
            }
        }
    }

    ////////////////////////////////////////

    protected boolean proxyConsumerFiltered_;

    protected boolean supplierAdminFiltered_;

    protected boolean consumerAdminFiltered_;

    protected boolean proxySupplierFiltered_;

    private FilterStage currentFilterStage_;

    private long receiveTimestamp_;

    ////////////////////////////////////////

    /**
     * get the Constraint Key for this Event. The Constraint Key is used to fetch the Filter
     * Constraints that must be evaluated for this Event. The Constraint Key consists of domain_name
     * and type_name of the Event.
     *
     * @return a <code>String</code> value
     */
    public abstract String getConstraintKey();

    public long getReceiveTimestamp()
    {
        return receiveTimestamp_;
    }

    /**
     * Access this NotificationEvent as Any.
     *
     * @return an <code>Any</code> value
     */
    public abstract Any toAny();

    /**
     * convert this message to a TypedEvent.
     *
     * @return a sequence of name-value pairs.
     * @throws NoTranslationException
     *             if the contents of the message cannot be translated into a TypedEvent.
     */
    public abstract Property[] toTypedEvent() throws NoTranslationException;

    /**
     * Access this NotificationEvent as StructuredEvent.
     *
     * @return a <code>StructuredEvent</code> value
     */
    public abstract StructuredEvent toStructuredEvent();

    /**
     * get the Type of this NotificationEvent. The value is one of
     * {@link org.jacorb.notification.interfaces.Message#TYPE_ANY},{@link
     * org.jacorb.notification.interfaces.Message#TYPE_STRUCTURED}, or {@link
     * org.jacorb.notification.interfaces.Message#TYPE_TYPED}.
     *
     * @return the Type of this NotificationEvent.
     */
    public abstract int getType();

    /**
     * Internal Reference Counter.
     */
    protected int referenced_ = 0;

    public synchronized final void reset()
    {
        referenced_ = 0;
        currentFilterStage_ = null;

        doReset();
    }

    protected void doReset()
    {
        // no operation
    }

    /**
     * Add a reference on this NotificationEvent. After Usage removeReference must be called.
     */
    public synchronized void addReference()
    {
        ++referenced_;
    }

    /**
     * release this NotificationEvent. If the internal Refcounter is zero the NotificationEvent is
     * returned to its pool.
     */
    protected synchronized void removeReference()
    {
        if (referenced_ > 0)
        {
            --referenced_;
        }

        if (referenced_ == 0)
        {
            super.dispose();
        }
    }

    public void setFilterStage(FilterStage node)
    {
        currentFilterStage_ = node;
    }

    public FilterStage getFilterStage()
    {
        return currentFilterStage_;
    }

    public EvaluationResult extractValue(EvaluationContext context,
            ComponentName componentRootNode, RuntimeVariable runtimeVariable)
            throws EvaluationException
    {
        EvaluationResult _ret = null;

        final String _completePath = componentRootNode.getComponentName();

        if (logger_.isDebugEnabled())
        {
            logger_.debug("extractValue: " + _completePath);
            logger_.debug("runtimeVariable=" + runtimeVariable);
        }

        _ret = context.lookupResult(_completePath);

        if (_ret == null)
        {
            _ret = runtimeVariable.evaluate(context);

            _ret = context.extractFromMessage(_ret, componentRootNode, runtimeVariable);

            context.storeResult(_completePath, _ret);
        }

        if (_ret == null)
        {
            throw new EvaluationException("Could not resolve " + _completePath);
        }

        return _ret;
    }

    public abstract EvaluationResult extractFilterableData(EvaluationContext context,
            ComponentName componentRootNode, String variable) throws EvaluationException;

    public abstract EvaluationResult extractVariableHeader(EvaluationContext context,
            ComponentName componentRootNode, String variable) throws EvaluationException;

    public EvaluationResult extractValue(EvaluationContext evaluationContext,
            ComponentName componentRootNode) throws EvaluationException
    {
        EvaluationResult _ret = null;

        final String _completeExpr = componentRootNode.getComponentName();

        if (logger_.isDebugEnabled())
        {
            logger_.debug("extractValue path: " + componentRootNode.toStringTree()
                    + "\n\tcomplete Expression=" + _completeExpr);
        }

        // check if the value is available in the cache
        _ret = evaluationContext.lookupResult(_completeExpr);

        if (logger_.isDebugEnabled())
        {
            logger_.debug("Cache READ: " + _ret);
        }

        if (_ret == null)
        {
            logger_.debug("Cache MISS");

            _ret = evaluationContext.extractFromMessage(this, componentRootNode);

            // Cache the EvaluationResult
            if (_ret != null)
            {
                if (logger_.isDebugEnabled())
                {
                    logger_.debug("Cache WRITE: " + _completeExpr + " => " + _ret);
                }
                evaluationContext.storeResult(_completeExpr, _ret);
            }
        }

        if (_ret == null)
        {
            throw new EvaluationException("Could not resolve " + _completeExpr);
        }

        return _ret;
    }

    public Message getHandle()
    {
        return new MessageHandle();
    }

    public void initReceiveTimestamp()
    {
        receiveTimestamp_ = System.currentTimeMillis();
    }

    public abstract boolean hasStartTime();

    public abstract long getStartTime();

    public abstract boolean hasStopTime();

    public abstract long getStopTime();

    public abstract boolean hasTimeout();

    public abstract long getTimeout();

    public abstract int getPriority();

    public abstract boolean match(Filter filter) throws UnsupportedFilterableData;

    public boolean match(FilterStage filterStage)
    {
        final List _filterList = filterStage.getFilters();

        if (_filterList.isEmpty())
        {
            return true;
        }

        final Iterator _filterIterator = _filterList.iterator();

        while (_filterIterator.hasNext())
        {
            try
            {
                final Filter _filter = (Filter) _filterIterator.next();

                if (match(_filter))
                {
                    return true;
                }
            } catch (UnsupportedFilterableData e)
            {
                // no problem
                // error means false
                logger_.info("unsupported filterable data. match result defaults to false.", e);
            }
            catch (Exception e)
            {
                logger_.warn("unexpected error during match. match result defaults to false", e);
            }
        }

        return false;
    }

    public abstract boolean match(MappingFilter filter, AnyHolder value)
            throws UnsupportedFilterableData;

    /**
     * Provide a Uniform Mapping from domain_name and type_name to a Key that can be used to put
     * EventTypes into a Map. if (d1 == d2) AND (t1 == t2) => calcConstraintKey(d1, t1) ==
     * calcConstraintKey(d2, t2).
     *
     * @param domain_name
     *            a <code>String</code> value
     * @param type_name
     *            a <code>String</code> value
     * @return an Unique Constraint Key.
     */
    public static String calcConstraintKey(String domain_name, String type_name)
    {
        if ("".equals(domain_name))
        {
            domain_name = "*";
        }

        if ("".equals(type_name))
        {
            type_name = "*";
        }

        final StringBuffer _buffer = new StringBuffer(domain_name);

        // insert a magic string-seperator
        // has no meaning though
        _buffer.append("_%_");
        _buffer.append(type_name);

        return _buffer.toString();
    }
}