/*
 * Copyright 2014-2020 Real Logic Limited.
 *
 * Licensed 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
 *
 * https://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 io.aeron.cluster;

import io.aeron.Counter;
import io.aeron.cluster.client.AeronCluster;
import io.aeron.exceptions.AeronException;
import io.aeron.test.Tests;
import org.agrona.ErrorHandler;
import org.agrona.ExpandableArrayBuffer;
import org.agrona.LangUtil;
import org.agrona.SystemUtil;
import org.agrona.concurrent.AgentTerminationException;
import org.agrona.concurrent.IdleStrategy;
import org.agrona.concurrent.YieldingIdleStrategy;

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;

class ClusterTests
{
    static final String HELLO_WORLD_MSG = "Hello World!";
    static final String NO_OP_MSG = "No op!           ";
    static final String REGISTER_TIMER_MSG = "Register a timer!";
    static final String ECHO_IPC_INGRESS_MSG = "Echo as IPC ingress";
    static final String UNEXPECTED_MSG = "Should never get this message!";

    private static final AtomicReference<Throwable> CLUSTER_ERROR = new AtomicReference<>();

    public static final Runnable TERMINATION_HOOK =
        () ->
        {
            throw new AgentTerminationException();
        };

    public static Runnable dynamicTerminationHook(
        final AtomicBoolean terminationExpected, final AtomicBoolean wasTerminated)
    {
        return () ->
        {
            if (null == terminationExpected || !terminationExpected.get())
            {
                throw new AgentTerminationException();
            }

            if (null != wasTerminated)
            {
                wasTerminated.set(true);
            }
        };
    }

    public static ErrorHandler errorHandler(final int nodeId)
    {
        return
            (ex) ->
            {
                if (ex instanceof AeronException)
                {
                    if (((AeronException)ex).category() == AeronException.Category.WARN)
                    {
                        //final String message = ex.getMessage();
                        //final String name = ex.getClass().getName();
                        //System.err.println("Warning in node " + nodeId + " " + name + " : " + message);
                        return;
                    }
                }

                if (ex instanceof AgentTerminationException)
                {
                    return;
                }

                addError(ex);

                System.err.println("\n*** Error in node " + nodeId + " followed by system thread dump ***\n\n");
                ex.printStackTrace();

                System.err.println();
                System.err.println(SystemUtil.threadDump());
            };
    }

    public static void addError(final Throwable ex)
    {
        final Throwable error = CLUSTER_ERROR.get();
        if (null == error)
        {
            CLUSTER_ERROR.set(ex);
        }
        else
        {
            error.addSuppressed(ex);
        }
    }

    public static void failOnClusterError()
    {
        final Throwable error = CLUSTER_ERROR.getAndSet(null);
        if (null != error)
        {
            LangUtil.rethrowUnchecked(error);
        }
    }

    public static void awaitElectionState(final Counter electionStateCounter, final Election.State state)
    {
        while (electionStateCounter.get() != state.code())
        {
            Tests.yield();
        }
    }

    public static Thread startMessageThread(final TestCluster cluster, final long backoffIntervalNs)
    {
        final Thread thread = new Thread(
            () ->
            {
                final IdleStrategy idleStrategy = YieldingIdleStrategy.INSTANCE;
                final AeronCluster client = cluster.client();
                final ExpandableArrayBuffer msgBuffer = cluster.msgBuffer();
                msgBuffer.putStringWithoutLengthAscii(0, HELLO_WORLD_MSG);

                while (!Thread.interrupted())
                {
                    if (client.offer(msgBuffer, 0, HELLO_WORLD_MSG.length()) < 0)
                    {
                        LockSupport.parkNanos(backoffIntervalNs);
                    }

                    idleStrategy.idle(client.pollEgress());
                }
            });

        thread.setDaemon(true);
        thread.setName("message-thread");
        thread.start();

        return thread;
    }
}