/*
 * Copyright 2002-2012 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
 *
 *      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 org.springframework.integration.samples.tcpclientserver;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.serializer.Deserializer;
import org.springframework.core.serializer.Serializer;

/**
 * This class is used to demonstrate how you can create a custom serializer/deserializer
 * to convert a TCP stream into custom objects which your domain-specific code can use.
 *
 * Since this is custom, it will have to have its own predefined assumptions for dealing
 * with the stream. In other words, there will have to be some indication within the
 * contents of the stream where the beginning/end is and how to extract the contents
 * into something meaningful (like an object). An example would be a fixed-file formatted
 * stream with the length encoded in some well known part of the stream (for example, the
 * first 8 bytes of the stream?).
 *
 * This custom serializer/deserializer assumes the first 3 bytes of the stream will be
 * considered an Order Number, the next 10 bytes will be the Sender's Name, the next 6 bytes
 * represents an left-zero-padded integer that specifies how long the rest of the message
 * content is. After that message content is parsed from the stream, the stream is assumed
 * to not have anything after it. In your code you could have delimiters to mark the end
 * of the stream, or could agree with the client that a valid stream is only n characters,
 * etc. Either way, since its custom, the client and server must have some predefined
 * assumptions in place for the communication to take place.
 *
 *
 * @author Christian Posta
 * @author Gunnar Hillert
 */
public class CustomSerializerDeserializer implements Serializer<CustomOrder>, Deserializer<CustomOrder>{

	protected final Log logger = LogFactory.getLog(this.getClass());

	private static final int ORDER_NUMBER_LENGTH = 3;
	private static final int SENDER_NAME_LENGTH = 10;
	private static final int MESSAGE_LENGTH_LENGTH = 6;

	/**
	 * Convert a CustomOrder object into a byte-stream
	 *
	 * @param object
	 * @param outputStream
	 * @throws IOException
	 */
	public void serialize(CustomOrder object, OutputStream outputStream) throws IOException {
		byte[] number = Integer.toString(object.getNumber()).getBytes();
		outputStream.write(number);

		byte[] senderName = object.getSender().getBytes();
		outputStream.write(senderName);

		String lenghtPadded = pad(6, object.getMessage().length());
		byte[] length = lenghtPadded.getBytes();
		outputStream.write(length);

		outputStream.write(object.getMessage().getBytes());
		outputStream.flush();
	}

	private String pad(int desiredLength, int length) {
		return StringUtils.leftPad(Integer.toString(length), desiredLength, '0');
	}

	/**
	 * Convert a raw byte stream into a CustomOrder
	 *
	 * @param inputStream
	 * @return
	 * @throws IOException
	 */
	public CustomOrder deserialize(InputStream inputStream) throws IOException {
		int orderNumber = parseOrderNumber(inputStream);
		String senderName = parseSenderName(inputStream);

		CustomOrder order = new CustomOrder(orderNumber, senderName);
		String message = parseMessage(inputStream);
		order.setMessage(message);
		return order;
	}

	private String parseMessage(InputStream inputStream) throws IOException {
		String lengthString = parseString(inputStream, MESSAGE_LENGTH_LENGTH);
		int lengthOfMessage = Integer.valueOf(lengthString);

		String message = parseString(inputStream, lengthOfMessage);
		return message;
	}

	private String parseString(InputStream inputStream, int length) throws IOException {
		StringBuilder builder = new StringBuilder();

		int c;
		for (int i = 0; i < length; ++i) {
			c = inputStream.read();
			checkClosure(c);
			builder.append((char)c);
		}

		return builder.toString();
	}

	private String parseSenderName(InputStream inputStream) throws IOException {
		return parseString(inputStream, SENDER_NAME_LENGTH);
	}

	private int parseOrderNumber(InputStream inputStream) throws IOException {
		String value = parseString(inputStream, ORDER_NUMBER_LENGTH);
		return Integer.valueOf(value.toString());
	}

	/**
	 * Check whether the byte passed in is the "closed socket" byte
	 * Note, I put this in here just as an example, but you could just extend the
	 * {@link org.springframework.integration.ip.tcp.serializer.AbstractByteArraySerializer} class
	 * which has this method
	 *
	 * @param bite
	 * @throws IOException
	 */
	protected void checkClosure(int bite) throws IOException {
		if (bite < 0) {
			logger.debug("Socket closed during message assembly");
			throw new IOException("Socket closed during message assembly");
		}
	}
}
