package io.openems.edge.bridge.modbus.api.worker.internal;

import static io.openems.common.test.TestUtils.createDummyClock;
import static io.openems.edge.bridge.modbus.api.worker.internal.CycleTasksManagerTest.LOG_HANDLER;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.time.temporal.ChronoUnit;

import org.junit.jupiter.api.Test;

import io.openems.common.exceptions.OpenemsException;
import io.openems.common.test.TimeLeapClock;
import io.openems.common.utils.FunctionUtils;
import io.openems.edge.bridge.modbus.DummyModbusComponent;
import io.openems.edge.bridge.modbus.api.worker.DummyReadTask;
import io.openems.edge.bridge.modbus.api.worker.DummyWriteTask;
import io.openems.edge.common.taskmanager.Priority;

public class TasksSupplierImplTest {

	private static final DummyReadTask RT_H_1 = new DummyReadTask("RT_H_1", 49, Priority.HIGH);
	private static final DummyReadTask RT_H_2 = new DummyReadTask("RT_H_2", 70, Priority.HIGH);
	private static final DummyReadTask RT_L_1 = new DummyReadTask("RT_L_1", 20, Priority.LOW);
	private static final DummyReadTask RT_L_2 = new DummyReadTask("RT_L_2", 30, Priority.LOW);
	private static final DummyWriteTask WT_1 = new DummyWriteTask("WT_1", 90);

	@Test
	public void testFull() throws OpenemsException {
		final var clock = createDummyClock();
		var defectiveComponents = new DefectiveComponents(clock, LOG_HANDLER);
		var sut = new TasksSupplierImpl(LOG_HANDLER);

		var component = new DummyModbusComponent();
		var protocol = component.getModbusProtocol();
		protocol.addTasks(RT_H_1, RT_H_2, RT_L_1, RT_L_2, WT_1);
		sut.addProtocol(component.id(), protocol, FunctionUtils::doNothing);

		// 1st Cycle
		var tasks = sut.getCycleTasks(defectiveComponents);
		assertEquals(4, tasks.reads().size() + tasks.writes().size());
		assertEquals(RT_L_1, tasks.reads().get(0));
		assertEquals(RT_H_1, tasks.reads().get(1));
		assertEquals(RT_H_2, tasks.reads().get(2));
		assertEquals(WT_1, tasks.writes().get(0));
		assertFalse(tasks.reads().contains(RT_L_2)); // -> not

		// 2nd Cycle
		tasks = sut.getCycleTasks(defectiveComponents);
		assertEquals(4, tasks.reads().size() + tasks.writes().size());
		assertEquals(RT_L_2, tasks.reads().get(0));
		assertEquals(RT_H_1, tasks.reads().get(1));
		assertEquals(RT_H_2, tasks.reads().get(2));
		assertEquals(WT_1, tasks.writes().get(0));
		assertFalse(tasks.reads().contains(RT_L_1)); // -> not

		// Add to defective
		defectiveComponents.add(component.id());

		// 3rd Cycle -> not yet due
		tasks = sut.getCycleTasks(defectiveComponents);
		assertEquals(0, tasks.reads().size() + tasks.writes().size());

		// 4th Cycle -> due: total one task
		clock.leap(30_001, ChronoUnit.MILLIS);
		tasks = sut.getCycleTasks(defectiveComponents);
		assertEquals(1, tasks.reads().size() + tasks.writes().size());

		// Remove from defective
		defectiveComponents.remove(component.id());

		// 5th Cycle -> back to normal
		tasks = sut.getCycleTasks(defectiveComponents);
		assertEquals(4, tasks.reads().size() + tasks.writes().size());

		// Finish
		sut.removeProtocol(component.id(), FunctionUtils::doNothing);
	}

	@Test
	public void testHighOnly() throws OpenemsException {
		var clock = new TimeLeapClock();
		var defectiveComponents = new DefectiveComponents(clock, LOG_HANDLER);
		var sut = new TasksSupplierImpl(LOG_HANDLER);

		var component = new DummyModbusComponent();
		var protocol = component.getModbusProtocol();
		protocol.addTasks(RT_H_1, RT_H_2, WT_1);
		sut.addProtocol(component.id(), protocol, FunctionUtils::doNothing);

		var tasks = sut.getCycleTasks(defectiveComponents);
		assertEquals(3, tasks.reads().size() + tasks.writes().size());
		assertTrue(tasks.reads().contains(RT_H_1));
		assertTrue(tasks.reads().contains(RT_H_2));
		assertTrue(tasks.writes().contains(WT_1));
	}

}
