﻿using System;
using System.Diagnostics.Contracts;

namespace Xero.Bus
{
    /// <summary>
    ///     Defines a subset of <see cref="Xero.Bus.IBus" /> for use in load balanced websites and WCF services.
    /// </summary>
    /// <remarks>
    ///     Websites and WCF services are restricted in the bus activities they may perform - they do not have access to all the methods on a
    ///     full Bus.
    /// </remarks>
    [ContractClass(typeof(ContractForISendOnlyBus))]
    public interface ISendOnlyBus
    {
        /// <summary>
        /// Publishes the event to subscribers.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <remarks>
        ///     Although from the website perspective it is just publishing the events,
        ///     internally we are actually wrapping each event in a <see cref="PublishEventCommand" />.
        ///     The command is sent to another process which is responsible for maintaining the
        ///     persistent store of subscribers and actually publishing the event.
        /// </remarks>
        void Publish<T>() where T : IEvent;

        /// <summary>
        /// Publishes the event to subscribers.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="event"></param>
        /// <remarks>
        ///     Although from the website perspective it is just publishing the events,
        ///     internally we are actually wrapping each event in a <see cref="PublishEventCommand" />.
        ///     The command is sent to another process which is responsible for maintaining the
        ///     persistent store of subscribers and actually publishing the event.
        /// </remarks>
        void Publish<T>(T @event) where T : IEvent;

        /// <summary>
        ///     Publishes the specified event.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="setEventProperties">An action which initializes properties of the event.</param>
        /// <remarks>
        ///     Typically this method is invoked when the event type is an interface.
        ///     This method allows the bus to provide an empty event instance - the <paramref name="setEventProperties" />
        ///     will be invoked to allow the consumer to populate the event properties.
        ///     Although from the website perspective it is just publishing the event,
        ///     internally we are actually wrapping the event in a <see cref="PublishEventCommand" />.
        ///     The command is sent to another process which is responsible for maintaining the
        ///     persistent store of subscribers and actually publishing the event.
        /// </remarks>
        void Publish<T>(Action<T> setEventProperties) where T : IEvent;

        /// <summary>
        ///     Sends the provided commands.
        /// </summary>
        /// <param name="command"></param>
        /// <remarks>
        ///     The commands will be sent to the destination configured for T.
        ///     The Xero Bus deliberately prevents the request/response pattern, therefore no callbacks (as per the standard
        ///     NServiceBus API) are provided.
        /// </remarks>
        void Send<T>(T command) where T : class, ICommand;

        /// <summary>
        ///     Instantiates a command of type T and sends it.
        /// </summary>
        /// <typeparam name="T">The type of command.</typeparam>
        /// <param name="setCommandProperties">An action which initializes properties of the command</param>
        /// <remarks>
        ///     The command will be sent to the destination configured for T.
        ///     The Xero Bus deliberately prevents the request/response pattern, therefore no callbacks (as per the standard
        ///     NServiceBus API) are provided.
        /// </remarks>
        void Send<T>(Action<T> setCommandProperties) where T : class, ICommand;
    }

    [ContractClassFor(typeof(ISendOnlyBus))]
    internal abstract class ContractForISendOnlyBus : ISendOnlyBus
    {
        public void Publish<T>() where T : IEvent {
        }

        public void Publish<T>(T @event) where T : IEvent {
            // ReSharper disable once CompareNonConstrainedGenericWithNull
            Contract.Requires(@event != null);        
        }

        public void Publish<T>(Action<T> setEventProperties) where T : IEvent {
            Contract.Requires(setEventProperties != null);
        }

        public void Send<T>(T command) where T : class, ICommand {
            Contract.Requires(command != null);
        }

        public void Send<T>(Action<T> setCommandProperties) where T : class, ICommand {
            Contract.Requires(setCommandProperties != null);
        }
    }
}