/*******************************************************************************
 * Copyright (c) 2011 Michael Mimo Moratti.
 *  
 * Michael Mimo Moratti licenses this file to you 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 ch.mimo.netty.handler.codec.icap.socket;

import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.MessageEvent;
import org.junit.Test;

import ch.mimo.netty.handler.codec.icap.DataMockery;
import ch.mimo.netty.handler.codec.icap.IcapChunk;
import ch.mimo.netty.handler.codec.icap.IcapChunkTrailer;
import ch.mimo.netty.handler.codec.icap.IcapRequest;
import ch.mimo.netty.handler.codec.icap.IcapResponse;
import ch.mimo.netty.handler.codec.icap.IcapResponseStatus;

public abstract class SocketTests extends AbstractSocketTest {
	
	private class SendOPTIONSRequestServerHandler extends AbstractHandler {
		@Override
		public boolean doMessageReceived(ChannelHandlerContext context, MessageEvent event, Channel channel) throws Exception {
			IcapRequest request = (IcapRequest)event.getMessage();
			DataMockery.assertCreateOPTIONSRequest(request);
			channel.write(DataMockery.createOPTIONSIcapResponse());
			return true;
		}
	}
	
	private class SendOPTIONSRequestClientHandler extends AbstractHandler {
		@Override
		public boolean doMessageReceived(ChannelHandlerContext context, MessageEvent event, Channel channel) throws Exception {
			IcapResponse response = (IcapResponse)event.getMessage();
			DataMockery.assertOPTIONSResponse(response);
			return true;
		}
	}
	
	private void sendOPTIONSRequest(PipelineType type) {
		runSocketTest(new SendOPTIONSRequestServerHandler(),new SendOPTIONSRequestClientHandler(),new Object[]{DataMockery.createOPTIONSIcapRequest()},type);
	}
	
	@Test
	public void sendOPTIONSRequestThroughClassicPipeline() {
		sendOPTIONSRequest(PipelineType.CLASSIC);
	}
	
	@Test
	public void sendOPTIONSRequestThroughCodecPipeline() {
		sendOPTIONSRequest(PipelineType.CODEC);
	}
	
	@Test
	public void sendOPTIONSRequestThroughTricklePipline() {
		sendOPTIONSRequest(PipelineType.TRICKLE);
	}
	
	private class SendRESPMODWithGetRequestNoBodyServerHandler extends AbstractHandler {
		@Override
		public boolean doMessageReceived(ChannelHandlerContext context, MessageEvent event, Channel channel) throws Exception {
			IcapRequest request = (IcapRequest)event.getMessage();
			DataMockery.assertCreateRESPMODWithGetRequestNoBody(request);
			channel.write(DataMockery.createRESPMODWithGetRequestNoBodyIcapResponse());
			return true;
		}
	}
	
	private class SendRESPMODWithGetRequestNoBodyClientHandler extends AbstractHandler {
		@Override
		public boolean doMessageReceived(ChannelHandlerContext context, MessageEvent event, Channel channel) throws Exception {
			IcapResponse response = (IcapResponse)event.getMessage();
			DataMockery.assertRESPMODWithGetRequestNoBodyResponse(response);
			return true;
		}
	}
	
	private void sendRESPMODWithGetRequestNoBody(PipelineType type) {
		runSocketTest(new SendRESPMODWithGetRequestNoBodyServerHandler(),new SendRESPMODWithGetRequestNoBodyClientHandler(),new Object[]{DataMockery.createRESPMODWithGetRequestNoBodyIcapMessage()},type);
	}
	
	@Test
	public void sendRESPMODWithGetRequestNoBodyThroughClassicPipeline() {
		sendRESPMODWithGetRequestNoBody(PipelineType.CLASSIC);
	}
	
	@Test
	public void sendRESPMODWithGetRequestNoBodyThroughCodecPipeline() {
		sendRESPMODWithGetRequestNoBody(PipelineType.CODEC);
	}
	
	@Test
	public void sendRESPMODWithGetRequestNoBodyThroughTricklePipeline() {
		sendRESPMODWithGetRequestNoBody(PipelineType.TRICKLE);
	}

	private class SendREQMODWithTwoBodyChunkServerHandler extends AbstractHandler {
		boolean requestMessage = false;
		boolean firstChunk = false;
		boolean secondChunk = false;
		boolean thirdChunk = false;
		@Override
		public boolean doMessageReceived(ChannelHandlerContext context, MessageEvent event, Channel channel) throws Exception {
			Object msg = event.getMessage();
			if(msg instanceof IcapRequest) {
				IcapRequest request = (IcapRequest)event.getMessage();
				DataMockery.assertCreateREQMODWithTwoChunkBody(request);
				requestMessage = true;
			} else if(msg instanceof IcapChunk) {
				IcapChunk chunk = (IcapChunk)msg;
				if(!firstChunk) {
					DataMockery.assertCreateREQMODWithTwoChunkBodyFirstChunk(chunk);
					firstChunk = true;
				} else if(firstChunk & !secondChunk) {
					DataMockery.assertCreateREQMODWithTwoChunkBodySecondChunk(chunk);
					secondChunk = true;
				} else if(firstChunk & secondChunk & !thirdChunk) {
					DataMockery.assertCreateREQMODWithTwoChunkBodyThirdChunk(chunk);
					channel.write(DataMockery.createREQMODWithTwoChunkBodyIcapResponse());
					channel.write(DataMockery.createREQMODWithTwoChunkBodyIcapChunkOne());
					channel.write(DataMockery.createREQMODWithTwoChunkBodyIcapChunkTwo());
					channel.write(DataMockery.createREQMODWithTwoChunkBodyIcapChunkThree());
					thirdChunk = true;
				}
			} else {
				fail("unexpected msg instance [" + msg.getClass().getCanonicalName() + "]");
			}
			return requestMessage & firstChunk & secondChunk & thirdChunk;
		}
	}
	
	private class SendREQMODWithTwoBodyChunkWithChunkAggregatorInPipelineServerHandler extends AbstractHandler {

		@Override
		public boolean doMessageReceived(ChannelHandlerContext context, MessageEvent event, Channel channel) throws Exception {
			Object msg = event.getMessage();
			if(msg instanceof IcapRequest) {
				IcapRequest request = (IcapRequest)event.getMessage();
				DataMockery.assertCreateREQMODWithTwoChunkBody(request);
				ChannelBuffer contentBuffer = request.getHttpRequest().getContent();
				String body = contentBuffer.toString(Charset.forName("ASCII"));
				StringBuilder builder = new StringBuilder();
				builder.append("This is data that was returned by an origin server.");
				builder.append("And this the second chunk which contains more information.");
				assertEquals("The body content was wrong",builder.toString(),body);
				channel.write(DataMockery.createREQMODWithTwoChunkBodyIcapResponse());
				channel.write(DataMockery.createREQMODWithTwoChunkBodyIcapChunkOne());
				channel.write(DataMockery.createREQMODWithTwoChunkBodyIcapChunkTwo());
				channel.write(DataMockery.createREQMODWithTwoChunkBodyIcapChunkThree());
				return true;
			}
			return false;
		}
	}
	
	private class SendREQMODWithTwoBodyChunkClientHandler extends AbstractHandler {
		boolean responseMessage = false;
		boolean firstChunk = false;
		boolean secondChunk = false;
		boolean thirdChunk = false;
		@Override
		public boolean doMessageReceived(ChannelHandlerContext context, MessageEvent event, Channel channel) throws Exception {
			Object msg = event.getMessage();
			if(msg instanceof IcapResponse) {
				IcapResponse response = (IcapResponse)event.getMessage();
				DataMockery.assertREQMODWithTwoChunkBodyResponse(response);
				responseMessage = true;
			} else if(msg instanceof IcapChunk) {
				IcapChunk chunk = (IcapChunk)msg;
				if(!firstChunk) {
					DataMockery.assertCreateREQMODWithTwoChunkBodyFirstChunk(chunk);
					firstChunk = true;
				} else if(firstChunk & !secondChunk) {
					DataMockery.assertCreateREQMODWithTwoChunkBodySecondChunk(chunk);
					secondChunk = true;
				} else if(firstChunk & secondChunk & !thirdChunk) {
					DataMockery.assertCreateREQMODWithTwoChunkBodyThirdChunk(chunk);
					thirdChunk = true;
				}
			} else {
				fail("unexpected msg instance [" + msg.getClass().getCanonicalName() + "]");
			}
			return responseMessage & firstChunk & secondChunk & thirdChunk;
		}
	}
	
	private void sendREQMODWithTwoBodyChunk(PipelineType type) {
		try {
			runSocketTest(new SendREQMODWithTwoBodyChunkServerHandler(),new SendREQMODWithTwoBodyChunkClientHandler(),new Object[]{DataMockery.createREQMODWithTwoChunkBodyIcapMessage(),
					DataMockery.createREQMODWithTwoChunkBodyIcapChunkOne(),DataMockery.createREQMODWithTwoChunkBodyIcapChunkTwo(),
					DataMockery.createREQMODWithTwoChunkBodyIcapChunkThree()},type);
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			fail("encoding error");
		}
	}
	
	private void sendREQMODWithTwoBodyChunkWithChunkAggregatorInPipeline() {
		try {
			runSocketTest(new SendREQMODWithTwoBodyChunkWithChunkAggregatorInPipelineServerHandler(),new SendREQMODWithTwoBodyChunkClientHandler(),new Object[]{DataMockery.createREQMODWithTwoChunkBodyIcapMessage(),
					DataMockery.createREQMODWithTwoChunkBodyIcapChunkOne(),DataMockery.createREQMODWithTwoChunkBodyIcapChunkTwo(),
					DataMockery.createREQMODWithTwoChunkBodyIcapChunkThree()},PipelineType.AGGREGATOR);
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			fail("encoding error");
		}
	}
	
	@Test
	public void sendREQMODWithTwoBodyChunkThroughClassicPipeline() {
		sendREQMODWithTwoBodyChunk(PipelineType.CLASSIC);
	}
	
	@Test
	public void sendREQMODWithTwoBodyChunkThroughCodecPipeline() {
		sendREQMODWithTwoBodyChunk(PipelineType.CODEC);
	}
	
	@Test
	public void sendREQMODWithTwoBodyChunkThroughTricklePipeline() {
		sendREQMODWithTwoBodyChunk(PipelineType.TRICKLE);
	}
	
	@Test
	public void sendREQMODWithTowBodyChunkThroughChunkAggregatorPipeline() {
		sendREQMODWithTwoBodyChunkWithChunkAggregatorInPipeline();
	}
	
	private class SendREQMODWithPreviewServerHandler extends AbstractHandler {
		boolean requestMessage = false;
		boolean firstChunk = false;
		boolean secondChunk = false;
		@Override
		public boolean doMessageReceived(ChannelHandlerContext context, MessageEvent event, Channel channel) throws Exception {
			Object msg = event.getMessage();
			if(msg instanceof IcapRequest) {
				IcapRequest request = (IcapRequest)event.getMessage();
				DataMockery.assertCreateREQMODWithPreview(request);
				requestMessage = true;
			} else if(msg instanceof IcapChunk) {
				IcapChunk chunk = (IcapChunk)msg;
				if(!firstChunk) {
					DataMockery.assertCreateREQMODWithPreviewChunk(chunk);
					firstChunk = true;
				} else if(firstChunk & !secondChunk) {
					DataMockery.assertCreateREQMODWithPreviewChunkLastChunk(chunk);
					channel.write(DataMockery.createREQMODWithPreviewAnnouncement204ResponseIcapMessage());
					secondChunk = true;
				} 
			} else {
				fail("unexpected msg instance [" + msg.getClass().getCanonicalName() + "]");
			}
			return requestMessage & firstChunk & secondChunk;
		}
	}
	
	private class SendREQMODWithPreviewAggregatorServerHandler extends AbstractHandler {

		@Override
		public boolean doMessageReceived(ChannelHandlerContext context, MessageEvent event, Channel channel) throws Exception {
			Object msg = event.getMessage();
			if(msg instanceof IcapRequest) {
				IcapRequest request = (IcapRequest)event.getMessage();
				DataMockery.assertCreateREQMODWithPreview(request);
				ChannelBuffer requestBodyBuffer = request.getHttpRequest().getContent();
				String body = requestBodyBuffer.toString(Charset.forName("ASCII"));
				StringBuilder builder = new StringBuilder();
				builder.append("This is data that was returned by an origin server.");
				assertEquals("The body content was wrong",builder.toString(),body);
				channel.write(DataMockery.createREQMODWithPreviewAnnouncement204ResponseIcapMessage());
				return true;
			} else {
				fail("unexpected msg instance [" + msg.getClass().getCanonicalName() + "]");
			}
			return false;
		}
		
	}
	
	private class SendREQMODWithPreviewClientHandler extends AbstractHandler {

		@Override
		public boolean doMessageReceived(ChannelHandlerContext context, MessageEvent event, Channel channel) throws Exception {
			Object msg = event.getMessage();
			if(msg instanceof IcapResponse) {
				IcapResponse response = (IcapResponse)msg;
				DataMockery.assertCreateREQMODWithPreviewAnnouncement204Response(response);
			} else {
				fail("unexpected msg instance [" + msg.getClass().getCanonicalName() + "]");
			}
			return true;
		}
	}
	
	private void sendREQMODWithPreview(PipelineType type) {
		try {
		runSocketTest(new SendREQMODWithPreviewServerHandler(),new SendREQMODWithPreviewClientHandler(),new Object[]{DataMockery.createREQMODWithPreviewAnnouncementIcapMessage(),
			DataMockery.createREQMODWithPreviewIcapChunk(),DataMockery.createREQMODWithPreviewLastIcapChunk()},type);
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			fail("encoding error");
		}
	}
	
	@Test
	public void sendREQMODWithPreviewThroughClassicPipeline() {
		sendREQMODWithPreview(PipelineType.CLASSIC);
	}
	
	@Test
	public void sendREQMODWithPreviewThroughCodecPipeline() {
		sendREQMODWithPreview(PipelineType.CODEC);
	}
	
	@Test
	public void sendREQMODWithPreviewThroughTricklePipeline() {
		sendREQMODWithPreview(PipelineType.TRICKLE);
	}
	
	@Test
	public void sendREQMODWithPreviewThroughAggregatorPipleline() {
		try {
			runSocketTest(new SendREQMODWithPreviewAggregatorServerHandler(),new SendREQMODWithPreviewClientHandler(),new Object[]{DataMockery.createREQMODWithPreviewAnnouncementIcapMessage(),
				DataMockery.createREQMODWithPreviewIcapChunk(),DataMockery.createREQMODWithPreviewLastIcapChunk()},PipelineType.AGGREGATOR);
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
				fail("encoding error");
			}
	}
	
	private class SendREQMODWithPreviewAndReturn100ContinueServerHandler extends AbstractHandler {
		boolean requestMessage = false;
		boolean firstChunk = false;
		boolean secondChunk = false;
		boolean thirdChunk = false;
		boolean fourthChunk = false;
		@Override
		public boolean doMessageReceived(ChannelHandlerContext context, MessageEvent event, Channel channel) throws Exception {
			Object msg = event.getMessage();
			if(msg instanceof IcapRequest) {
				IcapRequest request = (IcapRequest)event.getMessage();
				DataMockery.assertCreateREQMODWithPreview(request);
				requestMessage = true;
			} else if(msg instanceof IcapChunk) {
				IcapChunk chunk = (IcapChunk)msg;
				if(!firstChunk) {
					DataMockery.assertCreateREQMODWithPreviewChunk(chunk);
					firstChunk = true;
				} else if(firstChunk & !secondChunk) {
					DataMockery.assertCreateREQMODWithPreviewChunkLastChunk(chunk);
					channel.write(DataMockery.createREQMODWithPreviewAnnouncement100ContinueIcapMessage());
					secondChunk = true;
				} else if(firstChunk & secondChunk & !thirdChunk) {
					DataMockery.assertCreateREQMODWithPreview100ContinueChunk(chunk);
					thirdChunk = true;
				} else if(firstChunk & secondChunk & thirdChunk & !fourthChunk) {
					assertTrue("chunk is of wrong type",chunk instanceof IcapChunkTrailer);
					fourthChunk = true;
				}
			} else {
				fail("unexpected msg instance [" + msg.getClass().getCanonicalName() + "]");
			}
			return requestMessage & firstChunk & secondChunk & thirdChunk & fourthChunk;
		}
	}
	
	private class SendREQMODWithPreviewAndReturn100ContinueClientHandler extends AbstractHandler {

		@Override
		public boolean doMessageReceived(ChannelHandlerContext context, MessageEvent event, Channel channel) throws Exception {
			Object msg = event.getMessage();
			if(msg instanceof IcapResponse) {
				IcapResponse response = (IcapResponse)msg;
				assertEquals("wrong response type",IcapResponseStatus.CONTINUE,response.getStatus());
				channel.write(DataMockery.createREQMODWithPreview100ContinueIcapChunk());
				channel.write(DataMockery.createREQMODWithPreview100ContinueLastIcapChunk());
				return true;
			}
			return false;
		}
	}
	
	private void sendREQMODWithPreviewAndReturn100Continue(PipelineType type) {
		try {
		runSocketTest(new SendREQMODWithPreviewAndReturn100ContinueServerHandler(),new SendREQMODWithPreviewAndReturn100ContinueClientHandler(),new Object[]{DataMockery.createREQMODWithPreviewAnnouncementIcapMessage(),
			DataMockery.createREQMODWithPreviewIcapChunk(),DataMockery.createREQMODWithPreviewLastIcapChunk()},type);
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			fail("encoding error");
		}
	}
	
	@Test
	public void sendREQMODWithPreviewAndReturn100ContinueClassicPipleline() {
		sendREQMODWithPreviewAndReturn100Continue(PipelineType.CLASSIC);
	}
	
	@Test
	public void sendREQMODWithPreviewAndReturn100ContinueCodecPipleline() {
		sendREQMODWithPreviewAndReturn100Continue(PipelineType.CODEC);
	}
	
	@Test
	public void sendREQMODWithPreviewAndReturn100ContinueTricklePipleline() {
		sendREQMODWithPreviewAndReturn100Continue(PipelineType.TRICKLE);
	}

	private class SendREQMODWithGetRequestAndDataServerHandler extends AbstractHandler {

		private boolean requestReceived = false;
		private boolean dataReceived = false;
		
		@Override
		public boolean doMessageReceived(ChannelHandlerContext context, MessageEvent event, Channel channel) throws Exception {
			Object msg = event.getMessage();
			if(msg instanceof IcapRequest) {
				IcapRequest request = (IcapRequest)msg;
				requestReceived = true;
				dataReceived = request.getHttpRequest().getContent().readableBytes() > 0;
				channel.write(DataMockery.createREQMODWithDataIcapResponse());
			}
			return requestReceived & dataReceived;
		}
		
	}
	
	private class SendREQMODWithGetRequestAndDataClientHandler extends AbstractHandler {

		private boolean responseReceived = false;
		private boolean dataReceived = false;
		
		@Override
		public boolean doMessageReceived(ChannelHandlerContext context, MessageEvent event, Channel channel) throws Exception {
			Object msg = event.getMessage();
			if(msg instanceof IcapResponse) {
				IcapResponse response = (IcapResponse)msg;
				responseReceived = true;
				dataReceived = response.getHttpRequest().getContent().readableBytes() > 0;
			}
			return responseReceived & dataReceived;
		}
		
	}
	
	@Test
	public void aggregatorSeparatorCombinationTest() {
		runSocketTest(new SendREQMODWithGetRequestAndDataServerHandler(),new SendREQMODWithGetRequestAndDataClientHandler(),new Object[]{DataMockery.createREQMODWithGetRequestAndDataIcapMessage()},PipelineType.SEPARATOR_AGGREGATOR);
	}
	
	private class RepeaterServerHandler extends AbstractHandler {

		private int counter = 0;
		
		@Override
		public boolean doMessageReceived(ChannelHandlerContext context, MessageEvent event, Channel channel) throws Exception {
			Object msg = event.getMessage();
			if(msg instanceof IcapRequest) {
				IcapRequest request = (IcapRequest)msg;
				DataMockery.assertCreateREQMODWithPostRequestAndDataIcapRequest(request);
				IcapResponse response = DataMockery.createREQMODWithPostRequestIcapResponse();
				if(counter >= 100) {
					response.addHeader("TEST","END");
				}
				channel.write(response);
				counter++;
			}
			return counter >= 100;
		}
		
	}
	
	private class RepeaterClientHandler extends AbstractHandler {

		private boolean end;
		
		@Override
		public boolean doMessageReceived(ChannelHandlerContext context, MessageEvent event, Channel channel) throws Exception {
			Object msg = event.getMessage();
			if(msg instanceof IcapResponse) {
				IcapResponse response = (IcapResponse)msg;
				DataMockery.assertCreateREQMODWithPostRequestAndDataIcapResponse(response);
				if(response.containsHeader("TEST")) {
					end = true;
				} else {
					channel.write(DataMockery.createREQMODWithPostRequestAndDataIcapMessage());
				}
			}
			return end;
		}
		
	}
	
	@Test
	public void aggregatorSeparatorCombinationRepeatTest() {
		runSocketTest(new RepeaterServerHandler(),new RepeaterClientHandler(),new Object[]{DataMockery.createREQMODWithPostRequestAndDataIcapMessage()},PipelineType.SEPARATOR_AGGREGATOR);

	}
}

