/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *
 */
package org.apache.polygene.library.alarm;

import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
import org.apache.polygene.api.identity.Identity;
import org.apache.polygene.api.injection.scope.Structure;
import org.apache.polygene.api.mixin.Mixins;
import org.apache.polygene.api.value.ValueBuilder;
import org.apache.polygene.api.value.ValueBuilderFactory;

/**
 * The Standard Model is centered around the Normal, Activated, Acknowledged
 * Deactivated, Reactivated, Blocked and Disabled states, and the triggers "activate", "deactivate",
 * "acknowledge", "block", "unblock", "enable" and "disable". The following matrix details the
 * resulting grid.
 * <table summary="Transitions">
 * <tr><th>Initial State</th><th>Trigger</th><th>Resulting State</th><th>Event Generated</th></tr>
 * <tr><td>Normal</td><td>activate</td><td>Activated</td><td>activation</td></tr>
 * <tr><td>Normal</td><td>deactivate</td><td>Normal</td><td>-</td></tr>
 * <tr><td>Normal</td><td>acknowledge</td><td>Normal</td><td>-</td></tr>
 * <tr><td>Normal</td><td>block</td><td>Blocked</td><td>block</td></tr>
 * <tr><td>Normal</td><td>unblock</td><td>Normal</td><td>-</td></tr>
 * <tr><td>Activated</td><td>activate</td><td>Activated</td><td>-</td></tr>
 * <tr><td>Activated</td><td>deactivate</td><td>Deactivated</td><td>deactivation</td></tr>
 * <tr><td>Activated</td><td>acknowledge</td><td>Acknowledged</td><td>acknowledge</td></tr>
 * <tr><td>Activated</td><td>block</td><td>Blocked</td><td>block</td></tr>
 * <tr><td>Activated</td><td>unblock</td><td>Activated</td><td>-</td></tr>
 * <tr><td>Deactivated</td><td>activate</td><td>Activated</td><td>activation</td></tr>
 * <tr><td>Deactivated</td><td>deactivate</td><td>Deativated</td><td>-</td></tr>
 * <tr><td>Deactivated</td><td>acknowledge</td><td>Normal</td><td>acknowledge</td></tr>
 * <tr><td>Deactivated</td><td>block</td><td>Blocked</td><td>block</td></tr>
 * <tr><td>Deactivated</td><td>unblock</td><td>Deactivated</td><td>-</td></tr>
 * <tr><td>Acknowledged</td><td>activate</td><td>Acknowledged</td><td>-</td></tr>
 * <tr><td>Acknowledged</td><td>deactivate</td><td>Normal</td><td>deactivation</td></tr>
 * <tr><td>Acknowledged</td><td>acknowledge</td><td>Acknowledged</td><td>-</td></tr>
 * <tr><td>Acknowledged</td><td>block</td><td>Blocked</td><td>block</td></tr>
 * <tr><td>Acknowledged</td><td>unblock</td><td>Acknowledged</td><td>-</td></tr>
 * <tr><td>Blocked</td><td>activate</td><td>Blocked</td><td>-</td></tr>
 * <tr><td>Blocked</td><td>deactivate</td><td>Blocked</td><td>-</td></tr>
 * <tr><td>Blocked</td><td>acknowledge</td><td>Blocked</td><td>-</td></tr>
 * <tr><td>Blocked</td><td>block</td><td>Blocked</td><td>-</td></tr>
 * <tr><td>Blocked</td><td>unblock</td><td>Normal</td><td>unblock</td></tr>
 * <tr><td>Normal</td><td>disable</td><td>Disabled</td><td>disable</td></tr>
 * <tr><td>Blocked</td><td>disable</td><td>Disabled</td><td>disable</td></tr>
 * <tr><td>Deactivated</td><td>disable</td><td>Disabled</td><td>disable</td></tr>
 * <tr><td>Acknowledged</td><td>disable</td><td>Disabled</td><td>disable</td></tr>
 * <tr><td>Activated</td><td>disable</td><td>Disabled</td><td>disable</td></tr>
 * <tr><td>Reactivated</td><td>disable</td><td>Disabled</td><td>disable</td></tr>
 * <tr><td>Disabled</td><td>disable</td><td>Disabled</td><td>-</td></tr>
 * <tr><td>Normal</td><td>enable</td><td>Normal</td><td>-</td></tr>
 * <tr><td>Blocked</td><td>enable</td><td>Blocked</td><td>-</td></tr>
 * <tr><td>Deactivated</td><td>enable</td><td>Deactivated</td><td>-</td></tr>
 * <tr><td>Acknowledged</td><td>enable</td><td>Acknowledged</td><td>-</td></tr>
 * <tr><td>Activated</td><td>enable</td><td>Activated</td><td>-</td></tr>
 * <tr><td>Reactivated</td><td>enable</td><td>Reactivated</td><td>-</td></tr>
 * <tr><td>Disabled</td><td>enable</td><td>Normal</td><td>enable</td></tr>
 * </table>
 */

@Mixins(ExtendedAlarmModelService.ExtendedAlarmModelMixin.class)
public interface ExtendedAlarmModelService
    extends AlarmModel
{
    class ExtendedAlarmModelMixin
        implements AlarmModel
    {
        private final static List<String> TRIGGER_LIST;

        private static final List<String> STATUS_LIST;

        @Structure
        private ValueBuilderFactory vbf;

        static
        {
            List<String> list1 = new ArrayList<>();
            list1.add( AlarmPoint.STATUS_NORMAL );
            list1.add( AlarmPoint.STATUS_ACTIVATED );
            list1.add( AlarmPoint.STATUS_DEACTIVATED );
            list1.add( AlarmPoint.STATUS_ACKNOWLEDGED );
            list1.add( AlarmPoint.STATUS_REACTIVATED );
            list1.add( AlarmPoint.STATUS_BLOCKED );
            list1.add( AlarmPoint.STATUS_DISABLED );
            STATUS_LIST = Collections.unmodifiableList( list1 );

            List<String> list2 = new ArrayList<>();
            list2.add( AlarmPoint.TRIGGER_ACTIVATE );
            list2.add( AlarmPoint.TRIGGER_DEACTIVATE );
            list2.add( AlarmPoint.TRIGGER_ACKNOWLEDGE );
            list2.add( AlarmPoint.TRIGGER_BLOCK );
            list2.add( AlarmPoint.TRIGGER_UNBLOCK );
            list2.add( AlarmPoint.TRIGGER_DISABLE);
            list2.add( AlarmPoint.TRIGGER_ENABLE );
            TRIGGER_LIST = Collections.unmodifiableList( list2 );
        }

        static ResourceBundle getResourceBundle( Locale locale )
        {
            if( locale == null )
            {
                locale = Locale.getDefault();
            }
            ClassLoader cl = ExtendedAlarmModelMixin.class.getClassLoader();
            return ResourceBundle.getBundle( MODEL_BUNDLE_NAME, locale, cl );
        }

        /**
         * Returns the Name of the AlarmModel.
         * This normally returns the human readable technical name of
         * the AlarmModel.
         *
         * @return The system name of this alarm model.
         */
        @Override
        public String modelName()
        {
            return "org.apache.polygene.library.alarm.model.extended";
        }

        /**
         * Returns a Description of the AlarmModel in the default Locale.
         * This normally returns a full Description of the AlarmModel in the
         * default Locale.
         *
         * @return the description of the ModelProvider, in the default locale.
         */
        @Override
        public String modelDescription()
        {
            return modelDescription( null );
        }

        /**
         * Returns a Description of the AlarmModel.
         * This normally returns a full Description of the AlarmModel in the
         * Locale. If Locale is <code><b>null</b></code>, then the
         * default Locale is used.
         *
         * @param locale The locale that should be used for the description, or null for the default locale.
         *
         * @return The human readable, in the given locale, description of this alarm model.
         */
        @Override
        public String modelDescription( Locale locale )
        {
            ResourceBundle rb = getResourceBundle( locale );
            return rb.getString( "MODEL_DESCRIPTION_EXTENDED" );
        }

        /**
         * Execute the required changes upon an AlarmTrigger.
         * The AlarmSystem calls this method, for the AlarmStatus
         * in the the AlarmPoint to be updated, as well as an AlarmEvent
         * to be created.
         *
         * @param alarm   the AlarmPoint object to be updated.
         * @param trigger the AlarmTrigger that was used.
         */
        @Override
        public AlarmEvent evaluate( AlarmPoint alarm, String trigger )
        {
            if( trigger.equals( AlarmPoint.TRIGGER_ACTIVATE ) )
            {
                return activation( alarm );
            }
            else if( trigger.equals( AlarmPoint.TRIGGER_DEACTIVATE ) )
            {
                return deactivation( alarm );
            }
            else if( trigger.equals( AlarmPoint.TRIGGER_ACKNOWLEDGE ) )
            {
                return acknowledge( alarm );
            }
            else if( trigger.equals( AlarmPoint.TRIGGER_BLOCK ) )
            {
                return block( alarm );
            }
            else if( trigger.equals( AlarmPoint.TRIGGER_UNBLOCK ) )
            {
                return unblock( alarm );
            }
            else if( trigger.equals( AlarmPoint.TRIGGER_ENABLE ) )
            {
                return enable( alarm );
            }
            else if( trigger.equals( AlarmPoint.TRIGGER_DISABLE ) )
            {
                return disable( alarm );
            }
            else
            {
                throw new IllegalArgumentException( "'" + trigger + "' is not supported by this AlarmModel." );
            }
        }

        /**
         * Returns all the supported AlarmPoint triggers.
         *
         * @return The AlarmPoint triggers that this AlarmModel supports.
         */
        @Override
        public List<String> alarmTriggers()
        {
            return TRIGGER_LIST;
        }

        @Override
        public List<String> statusList()
        {
            return STATUS_LIST;  //To change body of implemented methods use File | Settings | File Templates.
        }

        @Override
        public String computeTrigger( AlarmStatus status, boolean condition )
        {
            if( condition )
            {
                if( ( status.name(null).equals( AlarmPoint.STATUS_DEACTIVATED ) ) ||
                    ( status.name(null).equals( AlarmPoint.STATUS_NORMAL ) ) )
                {
                    return AlarmPoint.TRIGGER_ACTIVATE;
                }
            }
            else
            {
                if( ( status.name(null).equals( AlarmPoint.STATUS_ACTIVATED ) ) ||
                    ( status.name(null).equals( AlarmPoint.STATUS_REACTIVATED ) ) ||
                    ( status.name(null).equals( AlarmPoint.STATUS_ACKNOWLEDGED ) ) )
                {
                    return AlarmPoint.TRIGGER_DEACTIVATE;
                }
            }
            return null;
        }

        @Override
        public boolean computeCondition( AlarmStatus status )
        {
            return ( status.name(null).equals( AlarmPoint.STATUS_ACTIVATED ) ) ||
                   ( status.name(null).equals( AlarmPoint.STATUS_REACTIVATED ) ) ||
                   ( status.name(null).equals( AlarmPoint.STATUS_ACKNOWLEDGED ) );
        }

        /**
         * StateMachine change for activate trigger.
         *
         * @param alarm the alarm that is being triggered.
         *
         * @return The event to be fired on activation.
         */
        private AlarmEvent activation( AlarmPoint alarm )
        {
            AlarmStatus status = alarm.currentStatus();
            if( ( status.name(null).equals( AlarmPoint.STATUS_NORMAL ) ) ||
                ( status.name(null).equals( AlarmPoint.STATUS_DEACTIVATED ) ) )
            {
                AlarmStatus newStatus = createStatus( AlarmPoint.STATUS_ACTIVATED );
                return createEvent( alarm.identity().get(), status, newStatus, AlarmPoint.EVENT_ACTIVATION );
            }
            return null;
        }

        /**
         * StateMachine change for activate trigger.
         *
         * @param alarm the alarm that is being triggered.
         *
         * @return The event to be fired on deactivation.
         */
        private AlarmEvent deactivation( AlarmPoint alarm )
        {
            AlarmStatus status = alarm.currentStatus();
            if( status.name(null).equals( AlarmPoint.STATUS_ACKNOWLEDGED ) )
            {
                AlarmStatus newStatus = createStatus( AlarmPoint.STATUS_NORMAL );
                return createEvent( alarm.identity().get(), status, newStatus, AlarmPoint.EVENT_DEACTIVATION );
            }
            else if( status.name(null).equals( AlarmPoint.STATUS_ACTIVATED ) )
            {
                AlarmStatus newStatus = createStatus( AlarmPoint.STATUS_DEACTIVATED );
                return createEvent( alarm.identity().get(), status, newStatus, AlarmPoint.EVENT_DEACTIVATION );
            }
            return null;
        }

        /**
         * StateMachine change for activate trigger.
         *
         * @param alarm the alarm that is being triggered.
         *
         * @return The event to be fired on acknowledge.
         */
        private AlarmEvent acknowledge( AlarmPoint alarm )
        {
            AlarmStatus status = alarm.currentStatus();
            if( status.name(null).equals( AlarmPoint.STATUS_DEACTIVATED ) )
            {
                AlarmStatus newStatus = createStatus( AlarmPoint.STATUS_NORMAL );
                return createEvent( alarm.identity().get(), status, newStatus, AlarmPoint.EVENT_ACKNOWLEDGEMENT );
            }
            else if( status.name(null).equals( AlarmPoint.STATUS_ACTIVATED ) )
            {
                AlarmStatus newStatus = createStatus( AlarmPoint.STATUS_ACKNOWLEDGED );
                return createEvent( alarm.identity().get(), status, newStatus, AlarmPoint.EVENT_ACKNOWLEDGEMENT );
            }
            return null;
        }

        /**
         * StateMachine change for block trigger.
         *
         * @param alarm the alarm that is being triggered.
         *
         * @return The event to be fired on acknowledge.
         */
        private AlarmEvent block( AlarmPoint alarm )
        {
            AlarmStatus status = alarm.currentStatus();
            if( status.name(null).equals( AlarmPoint.STATUS_BLOCKED ) ||
                status.name(null).equals( AlarmPoint.STATUS_DISABLED ) )
            {
                return null;
            }
            AlarmStatus newStatus = createStatus( AlarmPoint.STATUS_BLOCKED );
            return createEvent( alarm.identity().get(), status, newStatus, AlarmPoint.EVENT_BLOCKING );
        }

        /**
         * StateMachine change for unblock trigger.
         *
         * @param alarm the alarm that is being triggered.
         *
         * @return The event to be fired on acknowledge.
         */
        private AlarmEvent unblock( AlarmPoint alarm )
        {
            AlarmStatus status = alarm.currentStatus();
            if( status.name(null).equals( AlarmPoint.STATUS_BLOCKED ) )
            {
                AlarmStatus newStatus = createStatus( AlarmPoint.STATUS_NORMAL );
                return createEvent( alarm.identity().get(), status, newStatus, AlarmPoint.EVENT_UNBLOCKING );
            }
            return null;
        }

        /**
         * StateMachine change for disable trigger.
         *
         * @param alarm the alarm that is being triggered.
         *
         * @return The event to be fired on acknowledge.
         */
        private AlarmEvent disable( AlarmPoint alarm )
        {
            AlarmStatus status = alarm.currentStatus();
            if( status.name(null).equals( AlarmPoint.STATUS_DISABLED ) )
            {
                return null;
            }
            AlarmStatus newStatus = createStatus( AlarmPoint.STATUS_DISABLED );
            return createEvent( alarm.identity().get() , status, newStatus, AlarmPoint.EVENT_DISABLING );
        }

        /**
         * StateMachine change for unblock trigger.
         *
         * @param alarm the alarm that is being triggered.
         *
         * @return The event to be fired on acknowledge.
         */
        private AlarmEvent enable( AlarmPoint alarm )
        {
            AlarmStatus status = alarm.currentStatus();
            if( status.name(null).equals( AlarmPoint.STATUS_DISABLED ) )
            {
                AlarmStatus newStatus = createStatus( AlarmPoint.STATUS_NORMAL );
                return createEvent( ( alarm.identity().get() ), status, newStatus, AlarmPoint.EVENT_ENABLING );
            }
            return null;
        }

        private AlarmStatus createStatus( String status )
        {
            ValueBuilder<AlarmStatus> builder = vbf.newValueBuilder( AlarmStatus.class );
            AlarmStatus.State prototype = builder.prototypeFor(AlarmStatus.State.class);
            prototype.name().set( status );
            prototype.creationDate().set( Instant.now() );
            return builder.newInstance();
        }

        private AlarmEvent createEvent( Identity alarmId,
                                        AlarmStatus oldStatus,
                                        AlarmStatus newStatus,
                                        String eventSystemName
        )
        {
            ValueBuilder<AlarmEvent> builder = vbf.newValueBuilder( AlarmEvent.class );
            AlarmEvent prototype = builder.prototype();
            prototype.identity().set( alarmId );
            prototype.eventTime().set( Instant.now() );
            prototype.newStatus().set( newStatus );
            prototype.oldStatus().set( oldStatus );
            prototype.systemName().set( eventSystemName );
            return builder.newInstance();
        }
    }
}
