package tests;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import org.junit.Test;

import models.algebra.Expression;
import models.algebra.InvalidMessage;
import models.algebra.ParameterizedIdentifierIsFutureWork;
import models.algebra.UnificationFailed;
import models.algebra.ValueUndefined;
import models.algebra.Variable;
import models.algebra.Constant;
import models.dataConstraintModel.ChannelMember;
import models.dataConstraintModel.DataConstraintModel;
import models.dataConstraintModel.ResourcePath;
import models.dataFlowModel.DataTransferChannel;
import models.dataFlowModel.DataTransferModel;
import models.dataFlowModel.ResolvingMultipleDefinitionIsFutureWork;
import parser.Parser;
import parser.Parser.TokenStream;
import parser.exceptions.ExpectedColon;
import parser.exceptions.ExpectedDoubleQuotation;
import parser.exceptions.ExpectedRightBracket;
import parser.exceptions.WrongJsonExpression;
import simulator.Event;
import simulator.Resource;
import simulator.Simulator;
import simulator.SystemState;
import simulator.ResourceIdentifier;
import simulator.states.MapResourceState;

public class SimulatorTest {
	@Test
	public void test() {
		try {
			TokenStream stream = new Parser.TokenStream();
			Parser parser = new Parser(stream);
			
			// Construct a data transfer architecture model.
			DataTransferModel model = new DataTransferModel();
			
			ResourcePath customers = new ResourcePath("customers");						// "customers"
			ResourcePath customer = new ResourcePath(customers, 
								new Variable("uid", DataConstraintModel.typeString));	// "customers.{uid}"
			ResourcePath customer_off = new ResourcePath(customer, "off");				// "customers.{uid}.off"
			ResourcePath customer_add = new ResourcePath(customer, "add");				// "customers.{uid}.add"
			ResourcePath companies = new ResourcePath("companies");						// "companies"
			ResourcePath company = new ResourcePath(companies, 
								new Variable("cid", DataConstraintModel.typeString));	// "companies.{cid}"
			ResourcePath company_add = new ResourcePath(company, "add");				// "companies.{cid}.add"
			ResourcePath company2 = new ResourcePath(companies, 
					new Variable("cid2", DataConstraintModel.typeString));				// "companies.{cid2}"
			ResourcePath company2_add = new ResourcePath(company2, "add");				// "companies.{cid2}.add"
			ResourcePath companyA = new ResourcePath(companies, 
					new Constant("\"A\"", DataConstraintModel.typeString));				// "companies.A"
			ResourcePath companyA_add = new ResourcePath(companyA, "add");				// "companies.{cid2}.add"
			model.addResourcePath(customer_off);
			model.addResourcePath(customer_add);
			model.addResourcePath(company_add);
			model.addResourcePath(company2_add);
			model.addResourcePath(customers);
			model.addResourcePath(companies);
			
			// For channel CIO_AddCustomer
			DataTransferChannel cio_addCustomer = new DataTransferChannel("CIO_AddCustomer");	// add a customer
			ChannelMember customers1 = new ChannelMember(customers);
			stream.addLine("db:Map");
			Expression curStateExp0 = parser.parseTerm(stream, model);
			customers1.getStateTransition().setCurStateExpression(curStateExp0);
			stream.addLine("addCustomer(uid:Str, off:Str)");
			Expression messageExp0 = parser.parseTerm(stream, model);
			customers1.getStateTransition().setMessageExpression(messageExp0);
			stream.addLine("insert(db, uid, {\"off\": off})");
			Expression nextStateExp0 = parser.parseTerm(stream, model);
			customers1.getStateTransition().setNextStateExpression(nextStateExp0);
			cio_addCustomer.addChannelMemberAsOutput(customers1);
			
			// For channel CIO_AddCompany
			DataTransferChannel cio_addCompany = new DataTransferChannel("CIO_AddCompany");	// add a company
			ChannelMember companies1 = new ChannelMember(companies);
			stream.addLine("db:Map");
			Expression curStateExp1 = parser.parseTerm(stream, model);
			companies1.getStateTransition().setCurStateExpression(curStateExp1);
			stream.addLine("addCampany(cid:Str, add:Str)");
			Expression messageExp1 = parser.parseTerm(stream, model);
			companies1.getStateTransition().setMessageExpression(messageExp1);
			stream.addLine("insert(db, cid, {\"add\": add})");
			Expression nextStateExp1 = parser.parseTerm(stream, model);
			companies1.getStateTransition().setNextStateExpression(nextStateExp1);
			cio_addCompany.addChannelMemberAsOutput(companies1);		
			
			// For channel CIO_SetCustomerOff
			DataTransferChannel cio_setCustomerOff = new DataTransferChannel("CIO_SetCustomerOff", new Variable("uid"));	// set customer's office
			ChannelMember customer_off_1 = new ChannelMember(customer_off);
			stream.addLine("cid:Str");
			Expression curStateExp2 = parser.parseTerm(stream, model);
			customer_off_1.getStateTransition().setCurStateExpression(curStateExp2);
			stream.addLine("setOff(cid2:Str)");
			Expression messageExp2 = parser.parseTerm(stream, model);
			customer_off_1.getStateTransition().setMessageExpression(messageExp2);
			stream.addLine("cid2");
			Expression nextStateExp2 = parser.parseTerm(stream, model);
			customer_off_1.getStateTransition().setNextStateExpression(nextStateExp2);
			cio_setCustomerOff.addChannelMemberAsOutput(customer_off_1);
			
			// For channel CIO_SetCompanyAdd
			DataTransferChannel cio_setCompanyAdd = new DataTransferChannel("CIO_SetCompanyAdd", new Variable("cid"));	// set companie's address
			ChannelMember company_add_1 = new ChannelMember(company_add);
			stream.addLine("a1:Str");
			Expression curStateExp3 = parser.parseTerm(stream, model);
			company_add_1.getStateTransition().setCurStateExpression(curStateExp3);
			stream.addLine("setAdd(a2:Str)");
			Expression messageExp3 = parser.parseTerm(stream, model);
			company_add_1.getStateTransition().setMessageExpression(messageExp3);
			stream.addLine("a2");
			Expression nextStateExp3 = parser.parseTerm(stream, model);
			company_add_1.getStateTransition().setNextStateExpression(nextStateExp3);
			cio_setCompanyAdd.addChannelMemberAsOutput(company_add_1);		
			
			// For channel C
			DataTransferChannel c = new DataTransferChannel("C", new Variable("uid"));		// update customer's address
			ChannelMember customer_off_2 = new ChannelMember(customer_off);
			stream.addLine("cid:Str");
			Expression curStateExp4 = parser.parseTerm(stream, model);
			customer_off_2.getStateTransition().setCurStateExpression(curStateExp4);
			stream.addLine("sync(cid2:Str, add2:Str)");
			Expression messageExp4 = parser.parseTerm(stream, model);
			customer_off_2.getStateTransition().setMessageExpression(messageExp4);
			stream.addLine("cid2");
			Expression nextStateExp4 = parser.parseTerm(stream, model);
			customer_off_2.getStateTransition().setNextStateExpression(nextStateExp4);
			c.addChannelMemberAsInput(customer_off_2);
			
			ChannelMember company_add_2 = new ChannelMember(company2_add);
			stream.addLine("a1:Str");
			Expression curStateExp5 = parser.parseTerm(stream, model);
			company_add_2.getStateTransition().setCurStateExpression(curStateExp5);
			stream.addLine("sync(cid2:Str, add2:Str)");
			Expression messageExp5 = parser.parseTerm(stream, model);
			company_add_2.getStateTransition().setMessageExpression(messageExp5);
			stream.addLine("add2");
			Expression nextStateExp5 = parser.parseTerm(stream, model);
			company_add_2.getStateTransition().setNextStateExpression(nextStateExp5);
			c.addChannelMemberAsInput(company_add_2);

			ChannelMember customer_add_2 = new ChannelMember(customer_add);
			stream.addLine("a3:Str");
			Expression curStateExp6 = parser.parseTerm(stream, model);
			customer_add_2.getStateTransition().setCurStateExpression(curStateExp6);
			stream.addLine("sync(cid2:Str, add2:Str)");
			Expression messageExp6 = parser.parseTerm(stream, model);
			customer_add_2.getStateTransition().setMessageExpression(messageExp6);
			stream.addLine("add2");
			Expression nextStateExp6 = parser.parseTerm(stream, model);
			customer_add_2.getStateTransition().setNextStateExpression(nextStateExp6);
			c.addChannelMemberAsOutput(customer_add_2);
			
			model.addInputChannel(cio_addCustomer);
			model.addInputChannel(cio_addCompany);
			model.addInputChannel(cio_setCustomerOff);
			model.addInputChannel(cio_setCompanyAdd);
			model.addChannel(c);
			
			Simulator simulator = new Simulator(model);
			
			// Initial state
			SystemState initialState = simulator.init();
			
			assertEquals(2, initialState.getRootResources().size());
			for (Resource rootRes: initialState.getRootResources()) {
				assertTrue(rootRes.getState() instanceof MapResourceState);
				assertEquals(0, rootRes.getChildren().size());
				assertEquals(0, ((MapResourceState) rootRes.getState()).getChildStates().size());
			}
			System.out.println("companies:" + initialState.getResource(ResourceIdentifier.createFrom(companies)).getState().getValue());
			System.out.println("customers:" + initialState.getResource(ResourceIdentifier.createFrom(customers)).getState().getValue());
			
			// Next state (companies.addCompany("A", "Osaka"))
			stream.addLine("addCompany(\"A\", \"Osaka\")");
			Expression messageAddComp = parser.parseTerm(stream, model);
			Event addCompany = new Event(cio_addCompany, messageAddComp, companies, initialState.getResource(ResourceIdentifier.createFrom(companies)));
			simulator.transition(addCompany);
			SystemState nextState = simulator.getCurState();
			
			assertEquals(2, nextState.getRootResources().size());
			Resource companiesRes = nextState.getResource(ResourceIdentifier.createFrom(companies));
			assertTrue(companiesRes.getState() instanceof MapResourceState);
			assertEquals(1, companiesRes.getChildren().size());
			assertEquals(1, ((MapResourceState) companiesRes.getState()).getChildStates().size());
			System.out.println("companies:" + nextState.getResource(ResourceIdentifier.createFrom(companies)).getState().getValue());
			System.out.println("customers:" + nextState.getResource(ResourceIdentifier.createFrom(customers)).getState().getValue());
			
			// After the next state (customers.addCustomer("1", "A"))
			stream.addLine("addCustomer(\"1\", \"A\")");
			Expression messageAddCust = parser.parseTerm(stream, model);
			Event addCustomer = new Event(cio_addCustomer, messageAddCust, customers, nextState.getResource(ResourceIdentifier.createFrom(customers)));			
			simulator.transition(addCustomer);
			SystemState nextNextState = simulator.getCurState();
			
			assertEquals(2, nextState.getRootResources().size());
			Resource customersRes = nextNextState.getResource(ResourceIdentifier.createFrom(customers));
			assertTrue(customersRes.getState() instanceof MapResourceState);
			assertEquals(1, customersRes.getChildren().size());
			assertEquals(1, ((MapResourceState) customersRes.getState()).getChildStates().size());
			System.out.println("companies:" + nextNextState.getResource(ResourceIdentifier.createFrom(companies)).getState().getValue());
			System.out.println("customers:" + nextNextState.getResource(ResourceIdentifier.createFrom(customers)).getState().getValue());
			
			// After the next next state (companies.A.setAdd("Tokyo"))
			stream.addLine("setAdd(\"Tokyo\")");
			Expression messageSetAdd = parser.parseTerm(stream, model);
			Event setAdd = new Event(cio_setCompanyAdd, messageSetAdd, company_add, nextNextState.getResource(ResourceIdentifier.createFrom(companyA_add)));			
			simulator.transition(setAdd);
			SystemState nextNextNextState = simulator.getCurState();
			Resource customersRes2 = nextNextNextState.getResource(ResourceIdentifier.createFrom(customers));
			assertTrue(customersRes2.getState() instanceof MapResourceState);
			assertEquals(1, customersRes2.getChildren().size());
			assertEquals(1, ((MapResourceState) customersRes2.getState()).getChildStates().size());
			System.out.println("companies:" + nextNextNextState.getResource(ResourceIdentifier.createFrom(companies)).getState().getValue());
			System.out.println("customers:" + nextNextNextState.getResource(ResourceIdentifier.createFrom(customers)).getState().getValue());
			
		} catch (ExpectedRightBracket | WrongJsonExpression | ExpectedColon | ParameterizedIdentifierIsFutureWork | ResolvingMultipleDefinitionIsFutureWork | InvalidMessage | UnificationFailed | ValueUndefined | ExpectedDoubleQuotation e) {
			e.printStackTrace();
		}
	}
}
