/*
 * Copyright 2015 the original author or authors.
 *
 * 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
 *
 * 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.springframework.statemachine.test.support;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;

import org.springframework.messaging.Message;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.listener.StateMachineListener;
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
import org.springframework.statemachine.state.State;
import org.springframework.statemachine.transition.Transition;

/**
 * A {@link StateMachineListener} which is used during the tests
 * to assert correct count of listener callbacks.
 *
 * @author Janne Valkealahti
 *
 * @param <S> the type of state
 * @param <E> the type of event
 */
public class LatchStateMachineListener<S, E> extends StateMachineListenerAdapter<S, E> {

	private final Object lock = new Object();

	private volatile CountDownLatch stateChangedLatch = new CountDownLatch(1);
	private volatile CountDownLatch stateEnteredLatch = new CountDownLatch(1);
	private volatile CountDownLatch stateExitedLatch = new CountDownLatch(1);
	private volatile CountDownLatch eventNotAcceptedLatch = new CountDownLatch(1);
	private volatile CountDownLatch transitionLatch = new CountDownLatch(1);
	private volatile CountDownLatch transitionStartedLatch = new CountDownLatch(1);
	private volatile CountDownLatch transitionEndedLatch = new CountDownLatch(1);
	private volatile CountDownLatch stateMachineStartedLatch = new CountDownLatch(1);
	private volatile CountDownLatch stateMachineStoppedLatch = new CountDownLatch(1);
	private volatile CountDownLatch extendedStateChangedLatch = new CountDownLatch(1);

	private final List<StateChangedWrapper<S, E>> stateChanged = new ArrayList<StateChangedWrapper<S, E>>();
	private final List<State<S, E>> stateEntered = new ArrayList<State<S, E>>();
	private final List<State<S, E>> stateExited = new ArrayList<State<S, E>>();
	private final List<Message<E>> eventNotAccepted = new ArrayList<Message<E>>();
	private final List<Transition<S, E>> transition = new ArrayList<Transition<S, E>>();
	private final List<Transition<S, E>> transitionStarted = new ArrayList<Transition<S, E>>();
	private final List<Transition<S, E>> transitionEnded = new ArrayList<Transition<S, E>>();
	private final List<StateMachine<S, E>> stateMachineStarted = new ArrayList<StateMachine<S, E>>();
	private final List<StateMachine<S, E>> stateMachineStopped = new ArrayList<StateMachine<S, E>>();
	private final List<ExtendedStateChangedWrapper> extendedStateChanged = new ArrayList<ExtendedStateChangedWrapper>();

	@Override
	public void stateChanged(State<S, E> from, State<S, E> to) {
		synchronized (lock) {
			this.stateChanged.add(new StateChangedWrapper<>(from, to));
			this.stateChangedLatch.countDown();
		}
	}

	@Override
	public void stateEntered(State<S, E> state) {
		synchronized (lock) {
			this.stateEntered.add(state);
			this.stateEnteredLatch.countDown();
		}
	}

	@Override
	public void stateExited(State<S, E> state) {
		synchronized (lock) {
			this.stateExited.add(state);
			this.stateExitedLatch.countDown();
		}
	}

	@Override
	public void eventNotAccepted(Message<E> event) {
		synchronized (lock) {
			this.eventNotAccepted.add(event);
			this.eventNotAcceptedLatch.countDown();
		}
	}

	@Override
	public void transition(Transition<S, E> transition) {
		synchronized (lock) {
			this.transition.add(transition);
			this.transitionLatch.countDown();
		}
	}

	@Override
	public void transitionStarted(Transition<S, E> transition) {
		synchronized (lock) {
			this.transitionStarted.add(transition);
			this.transitionStartedLatch.countDown();
		}
	}

	@Override
	public void transitionEnded(Transition<S, E> transition) {
		synchronized (lock) {
			this.transitionEnded.add(transition);
			this.transitionEndedLatch.countDown();
		}
	}

	@Override
	public void stateMachineStarted(StateMachine<S, E> stateMachine) {
		synchronized (lock) {
			this.stateMachineStarted.add(stateMachine);
			this.stateMachineStartedLatch.countDown();
		}
	}

	@Override
	public void stateMachineStopped(StateMachine<S, E> stateMachine) {
		synchronized (lock) {
			this.stateMachineStopped.add(stateMachine);
			this.stateMachineStoppedLatch.countDown();
		}
	}

	@Override
	public void extendedStateChanged(Object key, Object value) {
		synchronized (lock) {
			this.extendedStateChanged.add(new ExtendedStateChangedWrapper(key, value));
			this.extendedStateChangedLatch.countDown();
		}
	}

	public void reset(int stateChangedCount, int stateEnteredCount, int stateExitedCount, int eventNotAcceptedCount,
			int transitionCount, int transitionStartedCount, int transitionEndedCount, int stateMachineStartedCount,
			int stateMachineStoppedCount, int extendedStateChangedCount) {
		synchronized (lock) {
			this.stateChangedLatch = new CountDownLatch(stateChangedCount);
			this.stateEnteredLatch = new CountDownLatch(stateEnteredCount);
			this.stateExitedLatch = new CountDownLatch(stateExitedCount);
			this.eventNotAcceptedLatch = new CountDownLatch(eventNotAcceptedCount);
			this.transitionLatch = new CountDownLatch(transitionCount);
			this.transitionStartedLatch = new CountDownLatch(transitionStartedCount);
			this.transitionEndedLatch = new CountDownLatch(transitionEndedCount);
			this.stateMachineStartedLatch = new CountDownLatch(stateMachineStartedCount);
			this.stateMachineStoppedLatch = new CountDownLatch(stateMachineStoppedCount);
			this.extendedStateChangedLatch = new CountDownLatch(extendedStateChangedCount);
			this.stateChanged.clear();
			this.stateEntered.clear();
			this.stateExited.clear();
			this.eventNotAccepted.clear();
			this.transition.clear();
			this.transitionStarted.clear();
			this.transitionEnded.clear();
			this.stateMachineStarted.clear();
			this.stateMachineStopped.clear();
			this.extendedStateChanged.clear();
		}
	}

	public CountDownLatch getStateChangedLatch() {
		return stateChangedLatch;
	}

	public CountDownLatch getStateEnteredLatch() {
		return stateEnteredLatch;
	}

	public CountDownLatch getStateExitedLatch() {
		return stateExitedLatch;
	}

	public CountDownLatch getEventNotAcceptedLatch() {
		return eventNotAcceptedLatch;
	}

	public CountDownLatch getTransitionLatch() {
		return transitionLatch;
	}

	public CountDownLatch getTransitionStartedLatch() {
		return transitionStartedLatch;
	}

	public CountDownLatch getTransitionEndedLatch() {
		return transitionEndedLatch;
	}

	public CountDownLatch getStateMachineStartedLatch() {
		return stateMachineStartedLatch;
	}

	public CountDownLatch getStateMachineStoppedLatch() {
		return stateMachineStoppedLatch;
	}

	public CountDownLatch getExtendedStateChangedLatch() {
		return extendedStateChangedLatch;
	}

	public List<StateChangedWrapper<S, E>> getStateChanged() {
		return stateChanged;
	}

	public List<State<S, E>> getStateEntered() {
		return stateEntered;
	}

	public List<State<S, E>> getStateExited() {
		return stateExited;
	}

	public List<Message<E>> getEventNotAccepted() {
		return eventNotAccepted;
	}

	public List<Transition<S, E>> getTransition() {
		return transition;
	}

	public List<Transition<S, E>> getTransitionStarted() {
		return transitionStarted;
	}

	public List<Transition<S, E>> getTransitionEnded() {
		return transitionEnded;
	}

	public List<StateMachine<S, E>> getStateMachineStarted() {
		return stateMachineStarted;
	}

	public List<StateMachine<S, E>> getStateMachineStopped() {
		return stateMachineStopped;
	}

	public List<ExtendedStateChangedWrapper> getExtendedStateChanged() {
		return extendedStateChanged;
	}

	public static class StateChangedWrapper<S, E> {
		final State<S, E> from;
		final State<S, E> to;

		public StateChangedWrapper(State<S, E> from, State<S, E> to) {
			this.from = from;
			this.to = to;
		}

	}

	public static class ExtendedStateChangedWrapper {
		final Object key;
		final Object value;

		public ExtendedStateChangedWrapper(Object key, Object value) {
			this.key = key;
			this.value = value;
		}

	}

}
