/*
 * #%L
 * PortfolioEffect - Quant Client
 * %%
 * Copyright (C) 2011 - 2015 Snowfall Systems, Inc.
 * %%
 * This file is part of PortfolioEffect Quant Client.
 * 
 * PortfolioEffect Quant Client is free software: you can redistribute 
 * it and/or modify it under the terms of the GNU General Public License 
 * as published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * PortfolioEffect Quant Client is distributed in the hope that it will
 * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
 * See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with PortfolioEffect Quant Client. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */
package com.portfolioeffect.quant.client;

import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.commons.lang.SerializationUtils;
import org.iq80.snappy.Snappy;
import org.openfast.Context;
import org.openfast.Message;
import org.openfast.MessageInputStream;
import org.openfast.MessageOutputStream;
import org.openfast.error.FastException;
import org.openfast.examples.MessageBlockReaderFactory;
import org.openfast.examples.MessageBlockWriterFactory;
import org.openfast.examples.OpenFastExample.Variant;
import org.openfast.session.Connection;
import org.openfast.session.Endpoint;
import org.openfast.session.FastConnectionException;
import org.openfast.session.tcp.TcpEndpoint;
import org.openfast.template.TemplateRegistry;
import org.openfast.template.loader.XMLMessageTemplateLoader;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.portfolioeffect.quant.client.message.CalculationStatusMessage;
import com.portfolioeffect.quant.client.message.ClientMessage;
import com.portfolioeffect.quant.client.message.LogoutResponse;
import com.portfolioeffect.quant.client.message.Reject;
import com.portfolioeffect.quant.client.message.ServiceMessage;
import com.portfolioeffect.quant.client.message.TestRequest;
import com.portfolioeffect.quant.client.message.TransmitDataListMessage;
import com.portfolioeffect.quant.client.message.TransmitDataRequest;
import com.portfolioeffect.quant.client.message.TransmitDataResponse;
import com.portfolioeffect.quant.client.message.ValidationResponse;
import com.portfolioeffect.quant.client.message.type.EncryptMethodType;
import com.portfolioeffect.quant.client.message.type.EncryptedPasswordMethodType;
import com.portfolioeffect.quant.client.message.type.FastMessageType;
import com.portfolioeffect.quant.client.message.util.ClientRequestMessageFactory;
import com.portfolioeffect.quant.client.message.util.CryptograhicUtils;
import com.portfolioeffect.quant.client.message.util.ServerResponseMessageFactory;
import com.portfolioeffect.quant.client.message.util.ServerResponseMessageParser;
import com.portfolioeffect.quant.client.model.ConnectFailedException;
import com.portfolioeffect.quant.client.model.PriceDataSet;
import com.portfolioeffect.quant.client.portfolio.ArrayCache;
import com.portfolioeffect.quant.client.portfolio.ArrayCacheType;
import com.portfolioeffect.quant.client.result.Metric;
import com.portfolioeffect.quant.client.util.Console;
import com.portfolioeffect.quant.client.util.DateTimeUtil;
import com.portfolioeffect.quant.client.util.MessageStrings;
import com.portfolioeffect.quant.client.util.MetricRefreshValue;
import com.portfolioeffect.quant.client.util.ProgressBar;
import com.portfolioeffect.quant.client.util.StopWatch;
import com.portfolioeffect.quant.client.util.MetricUpdateCallback;
import com.portfolioeffect.quant.client.util.SimpleMetricUpdateCallback;

public class ClientConnection {

	private static final int MILLISEC_IN_SECOND = 1000;
	public static final String STREAM_IS_ALREADY_RUNNING = "Stream is already running";
	private static final int TIME_WAIT_TOPRINT = 20;
	private static final String SUPPORTED_CHARSET = "US-ASCII";
	private static final int TEST_PORT_NUMBER = 3443;
	private static final int MAX_BLOCK_DIMENSION = 100000;
	private static final int USER_LAYER_TIMEOUT_SECONDS_ESTIMATE =  60 * 5;
	private static final int DATA_TRANSMIT_TIMEOUT_SECONDS_ESTIMATE = 60 * 5;
	private static final int LOGON_TIMEOUT_SECONDS = 30;
	private static final int SERVICE_TIMEOUT_SEC = 30;
	private static final int PORT_NUMBER = 443;
	private static final String TEMPLATES_FILE = "config/template-quant.xml";
	private static final int LOGON_ATTEMPT_COUNT = 9;
	private static final int DEFAULT_LOGON_TIMEOUT_SEC = 30;
	private static final int HEARTBEAT_INTERVAL = 30;
	private static final EncryptMethodType ENCRYPT_METHOD_TYPE = EncryptMethodType.NONE;
	
	private int restarTimeWait =  60*3;
	
	private AtomicBoolean isStreamEnabled = new AtomicBoolean(false);
	private AtomicBoolean isStreamRuning = new AtomicBoolean(false);
	
	private String apiKey;
	private String username;
	private String password;
	private String templatesFileName;
	private String host;
	private int port;
	private Connection connection;
	private MessageOutputStream out;
	private MessageInputStream in;
	private MessageBlockWriterFactory messageBlockWriterFactory;
	private MessageBlockReaderFactory messageBlockReaderFactory;
	private volatile boolean isLoggedOn = false;
	private volatile boolean isConnected = false;
	private boolean isMessageLoggingEnabled = true;
	private Thread inboundMessageRouter;
	private Endpoint endpoint;
	private TemplateRegistry templateRegistry;
	private int outboundMsgSeqNum;
	private int indicatorDefRequestNum;
	private volatile LinkedBlockingDeque<ClientMessage> clientMessageQueue;
	private volatile LinkedBlockingDeque<ServiceMessage> serviceMessageQueue;
	private StopWatch timeDataFast = new StopWatch();
	private StopWatch timeDataTransmit = new StopWatch();
	private boolean debugModeEnabled = false;
	private StringBuffer callStatus = new StringBuffer();
	private int groupSize = 1;
	private int progressBarI = 0;
	private int progressBarMax = 0;
	private Thread heartbeatMonitor;
	private ProgressBar progressBar = new ProgressBar();

	private MetricUpdateCallback streamRefreshCallback = null;
	private SimpleMetricUpdateCallback streamRefreshCallbackPureData = null;

	private long idClient;

	private static AtomicLong idClientGenerator;

	private static AtomicLong id;

	static {
		id = new AtomicLong();
		idClientGenerator = new AtomicLong();

	}

	public static long getNewId() {
		return id.incrementAndGet();
	}

	public long getIdClient() {
		return idClient;
	}

	public ClientConnection() {
		setMessageLoggingEnabled(false);
		setTemplatesFileName(TEMPLATES_FILE);
		setPort(PORT_NUMBER);
		idClient = idClientGenerator.getAndIncrement();

	}

	public long getIdC() {
		return idClient;
	}

	/**
	 * Establishes connection to the server. Should be followed by the a call to
	 * logon(), otherwise client will be disconneted shortly.
	 * 
	 * @throws IOException
	 * @throws FastConnectionException
	 */ 
	public void start() throws IOException, FastConnectionException {

		// load connection properties from persistent storage
		endpoint = new TcpEndpoint(host, port);

		// create XML message loader template and populate its parameters
		XMLMessageTemplateLoader loader = new XMLMessageTemplateLoader();
		loader.setLoadTemplateIdFromAuxId(true);

		loader.load(Thread.currentThread().getContextClassLoader().getResourceAsStream(templatesFileName));

		// get template registry from message loader and create opefast context
		templateRegistry = loader.getTemplateRegistry();
		Context context = new Context();
		context.setTemplateRegistry(templateRegistry);

		// create message block reader & writer
		messageBlockWriterFactory = new MessageBlockWriterFactory(Variant.DEFAULT, 0, false);
		messageBlockReaderFactory = new MessageBlockReaderFactory(Variant.DEFAULT, 0, false);

		// create connection
		connection = endpoint.connect();

		// create input stream
		in = new MessageInputStream(connection.getInputStream(), context);
		in.setBlockReader(messageBlockReaderFactory.create());

		// create output stream
		out = new MessageOutputStream(connection.getOutputStream(), context);
		out.setBlockWriter(messageBlockWriterFactory.create());

		isConnected = true;

		// Client connected to endpoint

		clientMessageQueue = new LinkedBlockingDeque<ClientMessage>();
		serviceMessageQueue = new LinkedBlockingDeque<ServiceMessage>();

		inboundMessageRouter = new Thread(new InboundMessageWorker());
		inboundMessageRouter.start();

		heartbeatMonitor = new Thread(new HeartbeatMonitor());
		heartbeatMonitor.start();

	}

	/**
	 * Closes existing open connection to the server.
	 */
	/*
	 * public void stop() {
	 * 
	 * if (isConnected) {
	 * 
	 * try { if (isLoggedOn()) { logout(30); }
	 * 
	 * } catch (Exception e) { throw new
	 * RuntimeException(MessageStrings.ERROR_STOP_CLIENT, e); }
	 * 
	 * isConnected = false;
	 * 
	 * }
	 * 
	 * }
	 */

	public void stop() {

		if (isConnected) {
			try {
				if (isLoggedOn()) {
					logout(LOGON_TIMEOUT_SECONDS);
				}
				endpoint.close();
				connection.close();

				inboundMessageRouter.join();

				serviceMessageQueue.offer(new ServiceMessage(System.currentTimeMillis(), true));
				// heartbeatMonitor.join();//it dont stop
				heartbeatMonitor.interrupt();

			} catch (Exception e) {
				throw new RuntimeException(MessageStrings.ERROR_STOP_CLIENT, e);
			}

			isConnected = false;
		}

	}

	public void logout() {
		if (isConnected && isLoggedOn()) {
			Message logoutMsg = ClientRequestMessageFactory.createLogoutRequest(templateRegistry, getOutboundMsgSequenceNumber(), System.currentTimeMillis());
			out.writeMessage(logoutMsg);
		}
	};

	/**
	 * Request client logon with credentials provided using setter methods.
	 * 
	 * @throws Exception
	 */

	public void logon() {
		try {
			logon(DEFAULT_LOGON_TIMEOUT_SEC);
		} catch (Exception e) {
			stop();
			// Console.writeStackTrace(e);
		}
	};

	public void logon(int timeoutSec) throws Exception {
		logon(timeoutSec, 0);
	}

	public boolean logon(int timeoutSec, int attemptCount) throws Exception {
		if (attemptCount > LOGON_ATTEMPT_COUNT)
			return false;

		String encryptedPassword = CryptograhicUtils.encrypt(password, apiKey);
		Message loginMsg = ClientRequestMessageFactory.createLogonRequest(templateRegistry, HEARTBEAT_INTERVAL, ENCRYPT_METHOD_TYPE, username,
				encryptedPassword, EncryptedPasswordMethodType.AES, encryptedPassword.length(), getOutboundMsgSequenceNumber(), System.currentTimeMillis());

		Message msg = sendAndAwaitResponse(loginMsg, timeoutSec);
		FastMessageType responseMessageType = getMessageType(msg);

		String curentHost = getHost();
		int curentPort = getPort();

		if (responseMessageType == FastMessageType.LOGOUT) {
			LogoutResponse logoutReponse = ServerResponseMessageParser.parseLogoutResponse(msg);
			throw new Exception(logoutReponse.getText());
		}

		setHost(curentHost);
		setPort(curentPort);

		if (attemptCount == 0 && !isLoggedOn()) {
			throw new ConnectFailedException();

		}

		return isLoggedOn;
	}

	/**
	 * Request client logout
	 */
	public void logout(int timeoutSec) {
		if (!isLoggedOn())
			return;
		Message logoutMsg = ClientRequestMessageFactory.createLogoutRequest(templateRegistry, getOutboundMsgSequenceNumber(), System.currentTimeMillis());
		try {
			sendAndAwaitResponse(logoutMsg, timeoutSec);
		} catch (Exception e) {
			isLoggedOn = false;
		}
	}

	public void progressBarIAdd(int value) {
		if (progressBarMax == 0) {
			progressBarMax = value * groupSize;
			progressBarI = 0;
		}
		progressBar.printCompletionStatus(progressBarI, progressBarMax);
		progressBarI += value;
		progressBar.printCompletionStatus(progressBarI, progressBarMax);
		if (progressBarI == progressBarMax) {
			createCallGroup(1);
		}
	}

	public void createCallGroup(int groupSize) {
		this.groupSize = groupSize;
		this.progressBarI = 0;
		this.progressBarMax = 0;
		progressBar.reset();
		progressBar.setScale(groupSize);

	}

	public void printProgressBar(double percent) {
		progressBar.printCompletionStatus(percent);
	}

	public void proggressBarOn() {
		progressBar.setON(true);
	}

	public void proggressBarOff() {
		progressBar.setON(false);
	}

	public void resetProgressBar() {
		progressBar.reset();
	}

	public void setHost(String host) {
		this.host = host;
		if (host.equals("localhost"))
			setPort(TEST_PORT_NUMBER);
		else
			setPort(PORT_NUMBER);
	}

	public Metric start(String username, String password, String apiKey, String remoteHostName) {

		clearStatus();
		stop();

		setMessageLoggingEnabled(false);
		setTemplatesFileName(TEMPLATES_FILE);

		if (remoteHostName.equals("localhost"))
			setPort(TEST_PORT_NUMBER);
		else
			setPort(PORT_NUMBER);

		setUsername(username);
		setPassword(password);
		setApiKey(apiKey);
		setHost(remoteHostName);

		try {
			return restart();
		} catch (Exception e) {
			this.stop();
			if (e.getMessage().contains(":"))
				return new Metric(e.getMessage().split(":")[1]);
			else
				return new Metric(e.getMessage());
			//return new Metric(MessageStrings.ERROR_CONNECT);
		}
//		try {
//			logon(LOGON_TIMEOUT_SECONDS);
//		} catch (Exception e) {
//			stop();
//			
//		}

		//return new Metric();
	}

	private void clearStatus() {
		callStatus.setLength(0);
	}

	public Metric restart() {
		int totalTime = 0;
		 
		int waitTime=-1;
		while(totalTime<restarTimeWait){
			waitTime++;
			stop();
			try {
					if(waitTime>=1){
						totalTime += waitTime;
						if (totalTime > TIME_WAIT_TOPRINT)
							Console.write(MessageStrings.CONNECTING);
	
						waitAndDots(waitTime, totalTime);
					}
			} catch (InterruptedException e) {
			}

			clearStatus();

			try {
				start();
				logon(LOGON_TIMEOUT_SECONDS);
			} catch (IOException e) {
				continue;
			} catch (FastConnectionException e) {
				continue;
			} catch (ConnectFailedException e) {
				continue;
			} catch (Exception e) {
				stop();
				return new Metric(e.getMessage());
			}
			if (isLoggedOn()) {
				if (totalTime > TIME_WAIT_TOPRINT)
					Console.writeln(MessageStrings.OK);

				return new Metric();
			}

		}
		if (!isLoggedOn()) {
			return new Metric(MessageStrings.ERROR_CONNECT);
		}
		return new Metric();

	}

	private void waitAndDots(int sec, int totalTime) throws InterruptedException {
		if (totalTime > TIME_WAIT_TOPRINT) {
			Console.write(".");
		}
		Thread.sleep(sec*MILLISEC_IN_SECOND);
	}

	public double getDataVolume(double[] price, int[] timeSec) {
		PriceDataSet data;
		try {
			data = new PriceDataSet(price, timeSec);
		} catch (Exception e) {
			return 0;
		}
		double a = data.toBinaryZipCompress().length;
		return a / 1024 / 1024;
	}

	public double getDataVolume(double[] price) {
		return getDataVolume(price, new int[0]);
	}

	public static boolean isPureAscii(String v) {
		CharsetEncoder asciiEncoder = Charset.forName(SUPPORTED_CHARSET).newEncoder();
		return asciiEncoder.canEncode(v);
	}

	public Metric validateStringRequest(String requestString) throws Exception {
		
		if (isStreamEnabled.get()) {
			return new Metric(STREAM_IS_ALREADY_RUNNING);
		}

		if (!isPureAscii(requestString))
			return new Metric("Request " + MessageStrings.NON_ASCII);

		String[] paramList = new String[0];

		Message msg = ClientRequestMessageFactory.createValidationRequest(getTemplateRegistry(), requestString, getOutboundMsgSequenceNumber(),
				System.currentTimeMillis());
		Message responseMsg = sendAndAwaitResponse(msg, USER_LAYER_TIMEOUT_SECONDS_ESTIMATE);

		ValidationResponse response = ServerResponseMessageParser.parseValidationResponse(responseMsg);
		if (response.getMsgType().equals("OK")) {

			Type mapType = new TypeToken<String[]>() {
			}.getType();
			Gson gson = new Gson();

			paramList = gson.fromJson(response.getMsgBody(), mapType);
		} else {
			return new Metric(response.getMsgBody());
		}

		Metric result = new Metric();
		ArrayCache pL = new ArrayCache(paramList);
		result.setData("positions", pL);
		return result;
	}

	public Metric transmitQuantity(String assetName, int[] dataInt, long[] time) throws Exception {

		if (isStreamEnabled.get()) {

			return new Metric(STREAM_IS_ALREADY_RUNNING);
		}
		boolean isFirstBlock = true;
		int position = 0;

		for (int i = 0; i < time.length / MAX_BLOCK_DIMENSION; i++) {
			int[] dataTransmit = new int[MAX_BLOCK_DIMENSION];
			long[] timeTransmit = new long[MAX_BLOCK_DIMENSION];

			System.arraycopy(time, position, timeTransmit, 0, MAX_BLOCK_DIMENSION);
			System.arraycopy(dataInt, position, dataTransmit, 0, MAX_BLOCK_DIMENSION);

			String type = "QUANTITY";
			if (!isFirstBlock) {
				type += ":+";
			} else
				isFirstBlock = false;

			String request = assetName;
			Message msg = ServerResponseMessageFactory.createTransmitDataRequest(getTemplateRegistry(), type, request, dataTransmit, timeTransmit,
					getOutboundMsgSequenceNumber(), System.currentTimeMillis());

			Message responseMsg = sendAndAwaitResponse(msg, DATA_TRANSMIT_TIMEOUT_SECONDS_ESTIMATE);

			TransmitDataResponse response = ServerResponseMessageParser.parseTransmitDataResponse(responseMsg);

			position += MAX_BLOCK_DIMENSION;

			if (!response.getMsgType().equals("OK"))
				throw new Exception(response.getMsgBody());

		}

		if (time.length % MAX_BLOCK_DIMENSION != 0) {
			int[] dataTransmit = new int[time.length % MAX_BLOCK_DIMENSION];
			long[] timeTransmit = new long[time.length % MAX_BLOCK_DIMENSION];

			System.arraycopy(time, position, timeTransmit, 0, time.length % MAX_BLOCK_DIMENSION);
			System.arraycopy(dataInt, position, dataTransmit, 0, dataInt.length % MAX_BLOCK_DIMENSION);

			String type = "QUANTITY";
			if (!isFirstBlock) {
				type += ":+";
			} else
				isFirstBlock = false;

			String request = assetName;
			Message msg = ServerResponseMessageFactory.createTransmitDataRequest(getTemplateRegistry(), type, request, dataTransmit, timeTransmit,
					getOutboundMsgSequenceNumber(), System.currentTimeMillis());

			Message responseMsg = sendAndAwaitResponse(msg, DATA_TRANSMIT_TIMEOUT_SECONDS_ESTIMATE);

			TransmitDataResponse response = ServerResponseMessageParser.parseTransmitDataResponse(responseMsg);

			if (!response.getMsgType().equals("OK"))
				throw new Exception(response.getMsgBody());

		}

		Metric result = new Metric();
		result.setMessage("NON");

		return result;
	}

	public boolean transmitStreamQuantity(String assetName, int quantity, long time) throws Exception {

		if(!isStreamRuning.get())
			return false;
		
	
		
		int[] dataTransmit = new int[] { quantity };
		long[] timeTransmit = new long[] { time };

		String type = "QUANTITY:stream";

		String request = assetName;
		Message msg = ServerResponseMessageFactory.createTransmitDataRequest(getTemplateRegistry(), type, request, dataTransmit, timeTransmit,
				getOutboundMsgSequenceNumber(), System.currentTimeMillis());

		send(msg);
		return true;
	}

	public Metric transmitUserPrice(String assetName, float[] dataFloat, long[] time) throws Exception {
		if (isStreamEnabled.get()) {
			return new Metric(STREAM_IS_ALREADY_RUNNING);
		}
		boolean isFirstBlock = true;
		int position = 0;

		for (int i = 0; i < time.length / MAX_BLOCK_DIMENSION; i++) {
			float[] dataTransmit = new float[MAX_BLOCK_DIMENSION];
			long[] timeTransmit = new long[MAX_BLOCK_DIMENSION];

			System.arraycopy(time, position, timeTransmit, 0, MAX_BLOCK_DIMENSION);
			System.arraycopy(dataFloat, position, dataTransmit, 0, MAX_BLOCK_DIMENSION);

			String type = "USER_PRICE";
			if (!isFirstBlock) {
				type += ":+";
			} else {
				isFirstBlock = false;
			}

			String request = assetName;
			Message msg = ServerResponseMessageFactory.createTransmitDataRequest(getTemplateRegistry(), type, request, dataTransmit, timeTransmit,
					getOutboundMsgSequenceNumber(), System.currentTimeMillis());

			Message responseMsg = sendAndAwaitResponse(msg, DATA_TRANSMIT_TIMEOUT_SECONDS_ESTIMATE);

			TransmitDataResponse response = ServerResponseMessageParser.parseTransmitDataResponse(responseMsg);

			position += MAX_BLOCK_DIMENSION;

			if (!response.getMsgType().equals("OK"))
				throw new Exception(response.getMsgBody());

		}

		if (time.length % MAX_BLOCK_DIMENSION != 0) {

			float[] dataTransmit = new float[time.length % MAX_BLOCK_DIMENSION];
			long[] timeTransmit = new long[time.length % MAX_BLOCK_DIMENSION];

			System.arraycopy(time, position, timeTransmit, 0, time.length % MAX_BLOCK_DIMENSION);
			System.arraycopy(dataFloat, position, dataTransmit, 0, time.length % MAX_BLOCK_DIMENSION);

			String type = "USER_PRICE";
			if (!isFirstBlock) {
				type += ":+";
			} else
				isFirstBlock = false;

			String request = assetName;
			Message msg = ServerResponseMessageFactory.createTransmitDataRequest(getTemplateRegistry(), type, request, dataTransmit, timeTransmit,
					getOutboundMsgSequenceNumber(), System.currentTimeMillis());

			Message responseMsg = sendAndAwaitResponse(msg, DATA_TRANSMIT_TIMEOUT_SECONDS_ESTIMATE);

			TransmitDataResponse response = ServerResponseMessageParser.parseTransmitDataResponse(responseMsg);

			if (!response.getMsgType().equals("OK"))
				throw new Exception(response.getMsgBody());

		}

		Metric result = new Metric();
		result.setMessage("NON");

		return result;

	}

	public Metric transmitDataList(String fromTime, String toTime, ArrayList<String> dataList, String windowLength, String priceSamplingInterval,
				String momentsModel, String trainingPeriodEnabled) throws Exception {
		
		if (isStreamEnabled.get()) {
			return new Metric(STREAM_IS_ALREADY_RUNNING);
		}

		for (String e : dataList){
			if (!isPureAscii(e))
				return new Metric("Position name " + MessageStrings.NON_ASCII);
			
			boolean isHystoryPrice = e.contains("h-") || e.contains("hI-");
			
			if(isHystoryPrice && fromTime.contains("#"))
				return new Metric("fromTime is not set");
			
			if(isHystoryPrice && toTime.contains("#"))
				return new Metric("toTime is not set");
		}

		timeDataFast.reset();
		timeDataTransmit.reset();

		String requestType = "CHECK_DATA";

		TransmitDataListMessage dataListMessage = new TransmitDataListMessage(dataList, windowLength, fromTime, toTime, priceSamplingInterval, momentsModel, trainingPeriodEnabled);

		Gson gson = new Gson();
		Type mapType = new TypeToken<TransmitDataListMessage>() {
		}.getType();
		String request = gson.toJson(dataListMessage, mapType);

		timeDataFast.start();
		Message msg = ServerResponseMessageFactory.createTransmitDataRequest(getTemplateRegistry(), requestType, request, getOutboundMsgSequenceNumber(),
				System.currentTimeMillis());
		timeDataFast.stop();
		timeDataTransmit.start();
		Message responseMsg = sendAndAwaitResponse(msg, DATA_TRANSMIT_TIMEOUT_SECONDS_ESTIMATE);

		timeDataTransmit.stop();
		timeDataFast.start();
		TransmitDataResponse response = ServerResponseMessageParser.parseTransmitDataResponse(responseMsg);
		timeDataFast.stop();

		if (response.getMsgType().equals("OK")) {

			Metric result = new Metric();
			result.setMessage(response.getMsgBody());

			return result;

		} else {
			return new Metric(response.getMsgBody());
		}
	}

	public Metric estimateEstimator(String metricType) throws Exception {

		if (isStreamEnabled.get()) {
			return new Metric(STREAM_IS_ALREADY_RUNNING);
		}
		
		HashMap<String, String> info = new HashMap<String, String>();
		ArrayCache resultValueList = null;
		ArrayCache resultTimeList = null;

		boolean isRun = true;
		boolean isFirstBlock = true;
		double percent = 0;

		int[] dimensions = null;
		progressBar.printCompletionStatus(percent);

		while (isRun) {

			clearStatus();
			Message responseMsg;
			if (isFirstBlock) {

				resultValueList = new ArrayCache(ArrayCacheType.DOUBLE_VECTOR);
				resultTimeList = new ArrayCache(ArrayCacheType.LONG_VECTOR);

				// Gson gson = new Gson();

				Message msg = ClientRequestMessageFactory.createNonparametricComputeRequest(getTemplateRegistry(), metricType, "", new double[1], new int[1],
						getOutboundMsgSequenceNumber(), System.currentTimeMillis());

				responseMsg = sendAndAwaitResponse(msg, USER_LAYER_TIMEOUT_SECONDS_ESTIMATE);

				isFirstBlock = false;
			} else {

				Message msg = ClientRequestMessageFactory.createNonparametricComputeRequest(getTemplateRegistry(), metricType, "#NEXT#", new double[1],
						new int[1], getOutboundMsgSequenceNumber(), System.currentTimeMillis());

				responseMsg = sendAndAwaitResponse(msg, USER_LAYER_TIMEOUT_SECONDS_ESTIMATE);

			}

			TransmitDataRequest response = ServerResponseMessageParser.parseTransmitDataRequest(responseMsg);

			if (response.getMsgType().contains("OK")) {

				Gson gson = new Gson();
				Type mapType = new TypeToken<CalculationStatusMessage>() {
				}.getType();

				CalculationStatusMessage statusMessg = gson.fromJson(response.getMsgType(), mapType);

				dimensions = statusMessg.getDimension();

				float[] data = response.getDataFloat();
				long[] time = response.getTime();

				resultValueList.writeAsDouble(data);

				resultTimeList.write(time);

				percent = Double.valueOf(response.getMsgBody());
				progressBar.printCompletionStatus(percent);

				if (response.getMsgType().contains("STOP")) {

					info = statusMessg.getResultInfo();

					isRun = false;
				}
			} else {

				throw new Exception(response.getMsgBody());
			}
		}

		if (dimensions != null)
			resultValueList.setDimensions(dimensions);

		Metric result = new Metric();
		result.setData("value", resultValueList);
		result.setData("time", resultTimeList);
		result.setInfo(info);

		return result;
	}

	
	

	public void stopStream() {
		isStreamEnabled.set(false);
		stop();
		batchMetricKeys = null;
		//streamRefreshCallback = null;
		//streamRefreshCallbackPureData =null;
		
	}

	private List<String> batchMetricKeys = null;

	public List<String> getBatchMetricKeys() {
		return batchMetricKeys;
	}

	public void setBatchMetricKeys(List<String> batchMetricKeys, long portfolioID) {
		this.batchMetricKeys = new ArrayList<String>();

		for (String e : batchMetricKeys) {
			if (e.charAt(0) == '{' && e.length() > 1) {
				this.batchMetricKeys.add("{" + "portfolioID:" + portfolioID + ", request:" + e);
			} else
				this.batchMetricKeys.add(e);
		}

		// this.batchMetricKeys = batchMetricKeys;
	}

	public Metric estimateTransactional(String metricType, String indexPosition, ArrayList<String> positionList, String params) throws Exception {

		if (metricType.contains("stream")) {
			if (isStreamEnabled.get()) {
				return new Metric(STREAM_IS_ALREADY_RUNNING);
			}
			isStreamEnabled.set(true);
		}

		HashMap<String, String> info = new HashMap<String, String>();

		boolean isRun = true;
		boolean isFirstBlock = true;
		double percent = 0;

		int[] dimensions = null;
		progressBar.printCompletionStatus(percent);

		ArrayCache batchValues[] = null;
		ArrayCache batchValuesTime[] = null;

		// ArrayCache resultValueList = new
		// ArrayCache(ArrayCacheType.DOUBLE_VECTOR);
		// ArrayCache resultTimeList = new
		// ArrayCache(ArrayCacheType.LONG_VECTOR);

		while (isRun) {

			clearStatus();
			Message responseMsg;
			if (isFirstBlock) {

				if (indexPosition.length() != 0)
					positionList.add(0, indexPosition);

				Gson gson = new Gson();

				String request = gson.toJson(positionList);
				
				if(debugModeEnabled){
					Console.writeln("Request--->");
					Console.writeln(metricType);
					Console.writeln(request);
					Console.writeln(params);
					Console.writeln(">---");
				}
				
				
				Message msg = ClientRequestMessageFactory.createTransactionalPortfolioComputeRequest(getTemplateRegistry(), metricType, request, params,
						getOutboundMsgSequenceNumber(), System.currentTimeMillis());

				responseMsg = sendAndAwaitResponse(msg, USER_LAYER_TIMEOUT_SECONDS_ESTIMATE);

			} else {

				Message msg = ClientRequestMessageFactory.createTransactionalPortfolioComputeRequest(getTemplateRegistry(), metricType, "#NEXT#", "",
						getOutboundMsgSequenceNumber(), System.currentTimeMillis());

				responseMsg = sendAndAwaitResponse(msg, USER_LAYER_TIMEOUT_SECONDS_ESTIMATE);

			}

			TransmitDataRequest response = ServerResponseMessageParser.parseTransmitDataRequest(responseMsg);

			if (response.getMsgType().contains("OK")) {

				Gson gson = new Gson();
				Type mapType = new TypeToken<CalculationStatusMessage>() {
				}.getType();

				CalculationStatusMessage statusMessg = gson.fromJson(response.getMsgType(), mapType);

				dimensions = statusMessg.getDimension();
				if (dimensions.length == 0)
					dimensions = new int[] { 1 };

				float[] data = response.getDataFloat();
				long[] time = response.getTime();
				
				if(debugModeEnabled){
					
					if(data.length>0)
						Console.writeln("RECEIVED DATA BLOCK("+ data.length+"): "+data[0]+"\t"+data[data.length-1]);
				
					
					if(time.length>0)
						Console.writeln("RECEIVED TIME BLOCK("+ time.length+"): "+ 
					  (new Timestamp(time[0] +DateTimeUtil.CLIENT_TIME_DELTA))
							+"\t"+
							(new Timestamp(time[time.length-1] +DateTimeUtil.CLIENT_TIME_DELTA)));
					
				
					
				}

				if (isFirstBlock) {

					// resultValueList = new
					// ArrayCache(ArrayCacheType.DOUBLE_VECTOR);
					// resultTimeList = new
					// ArrayCache(ArrayCacheType.LONG_VECTOR);

					// resultValueList.writeAsDouble(data);
					// resultTimeList.write(time);

					// resultValueList.setDimensions(dimensions);

					// batchValues =
					// ArrayCache.splitBatchDouble(resultValueList);
					// -----------------

					batchValues = new ArrayCache[dimensions.length];
					for (int i = 0; i < dimensions.length; i++) {
						if (dimensions[i] == 1)
							batchValues[i] = new ArrayCache(ArrayCacheType.DOUBLE_VECTOR);
						else
							batchValues[i] = new ArrayCache(ArrayCacheType.DOUBLE_MATRIX);

						batchValues[i].setDimensions(new int[] { dimensions[i] });
					}

					batchValuesTime = new ArrayCache[batchValues.length];
					for (int k = 0; k < batchValues.length; k++)
						batchValuesTime[k] = new ArrayCache(ArrayCacheType.LONG_VECTOR);

					for (int k = 0; k < batchValues.length; k++) {
						batchValuesTime[k].lockToWrite();
						batchValues[k].lockToWrite();
					}

					int len = 0;
					while (len < data.length) {
						for (int k = 0; k < dimensions.length; k++) {
							for (int m = 0; m < dimensions[k]; m++) {
								batchValues[k].writeNextDouble(data[len]);
								len++;
							}
						}
					}

					for (int k = 0; k < batchValues.length; k++)
						batchValuesTime[k].writeNextLong(time);

					for (int k = 0; k < batchValues.length; k++) {
						batchValuesTime[k].unlockToWrite();
						batchValues[k].unlockToWrite();
					}

					isFirstBlock = false;

				} else {

					for (int k = 0; k < batchValues.length; k++) {
						batchValuesTime[k].lockToWrite();
						batchValues[k].lockToWrite();
					}

					for (int k = 0; k < batchValues.length; k++)
						batchValuesTime[k].writeNextLong(time);

					int len = 0;
					while (len < data.length) {
						for (int k = 0; k < dimensions.length; k++) {
							for (int m = 0; m < dimensions[k]; m++) {
								batchValues[k].writeNextDouble(data[len]);
								len++;
							}
						}
					}

					for (int k = 0; k < batchValues.length; k++) {
						batchValuesTime[k].unlockToWrite();
						batchValues[k].unlockToWrite();
					}

				}

				percent = Double.valueOf(response.getMsgBody());
				progressBar.printCompletionStatus(percent);

				if (isStreamEnabled.get() && percent >= 1) {

					StreamWoker streamWoker = new StreamWoker(batchValues, batchValuesTime, dimensions, streamRefreshCallback, streamRefreshCallbackPureData);
					new Thread(streamWoker).start();
					// streamWoker.run();

					info = statusMessg.getResultInfo();
					isRun = false;

				}

				if (response.getMsgType().contains("STOP")) {

					info = statusMessg.getResultInfo();

					isRun = false;
				}
			} else {
				isStreamEnabled.set(false);
				throw new Exception(response.getMsgBody());
			}
		}

		Metric result = new Metric();
		result.setData("values", batchValues);
		result.setData("times", batchValuesTime);
		result.setInfo(info);
		
		
		return result;

	}

	public Metric getAllSymbolsList() throws Exception {

		Message responseMsg;
		Message msg = ClientRequestMessageFactory.createTransactionalPortfolioComputeRequest(getTemplateRegistry(), "ALL_SYMBOLS", "", "",
				getOutboundMsgSequenceNumber(), System.currentTimeMillis());

		responseMsg = sendAndAwaitResponse(msg, USER_LAYER_TIMEOUT_SECONDS_ESTIMATE);

		TransmitDataRequest response = ServerResponseMessageParser.parseTransmitDataRequest(responseMsg);
		Metric result = new Metric();

		if (response.getMsgType().contains("OK")) {

			byte data[] = response.getDataFloatByte();

			data = Snappy.uncompress(data, 0, data.length);
			Map<String, String[]> map = (Map<String, String[]>) SerializationUtils.deserialize(data);

			ArrayCache id = new ArrayCache(map.get("id"));
			ArrayCache description = new ArrayCache(map.get("description"));
			ArrayCache exchange = new ArrayCache(map.get("exchange"));

			result.setData("id", id);
			result.setData("description", description);
			result.setData("exchange", exchange);

		} else {

			throw new Exception(response.getMsgBody());
		}

		return result;
	}

	public Metric getComputeTimeLeft() {

		for (int i = 0; i < 3; i++) {
			Message responseMsg;

			try {
				Message msg = ClientRequestMessageFactory.createTransactionalPortfolioComputeRequest(getTemplateRegistry(), "TIME_LEFT", "", "",
						getOutboundMsgSequenceNumber(), System.currentTimeMillis());

				responseMsg = sendAndAwaitResponse(msg, USER_LAYER_TIMEOUT_SECONDS_ESTIMATE);

				TransmitDataRequest response = ServerResponseMessageParser.parseTransmitDataRequest(responseMsg);
				Metric result = new Metric();

				if (response.getMsgType().contains("OK")) {

					String data[] = response.getMsgBody().split("#");
					HashMap<String, String> info = new HashMap<String, String>();
					info.put("timeLeft", data[0]);
					info.put("timeMax", data[1]);
					result.setInfo(info);

				} else {

					throw new Exception(response.getMsgBody());
				}

				return result;

			} catch (Exception e) {

				Metric result = processException(e);
				if (result == null)
					continue;
				return result;
			}

		}
		return new Metric(MessageStrings.FAILED_SERVER_TIME_OUT);

	}

	private Metric processException(Exception e) {

		if (e instanceof ConnectFailedException) {

			Metric isRestarted = restart();
			if (isRestarted.hasError()) {
				resetProgressBar();
				return new Metric(isRestarted.getErrorMessage());
			}

			return null;
		}

		if (e.getMessage() == null || e.getMessage().contains("No data in cache") || e.getMessage().contains("null")) {

			Metric isRestarted = restart();
			if (isRestarted.hasError()) {
				resetProgressBar();
				return new Metric(isRestarted.getErrorMessage());
			}

			return null;

		}

		if (e.getMessage() == null) {
			Console.writeStackTrace(e);
			return new Metric(MessageStrings.ERROR_101);
		}

		resetProgressBar();
		return new Metric(e.getMessage());

	}

	private Message sendAndAwaitResponse(Message request, int timeoutSec) throws Exception {

		if (!isConnected)
			throw new ConnectFailedException();

		try {
			out.writeMessage(request);
		} catch (Exception e) {
			throw new ConnectFailedException();
		}

		ClientMessage clientMessage = clientMessageQueue.poll(timeoutSec, TimeUnit.SECONDS);
		clientMessageQueue.clear();

		if (clientMessage == null) {
			throw new ConnectFailedException();
		}
		if (clientMessage == null || clientMessage.isEmpty()) {

			throw new ConnectFailedException();
		} else if (clientMessage.isRejected()) {

			Reject reject = ServerResponseMessageParser.parseReject(clientMessage.getMessage());
			throw new Exception(reject.getText());
		}

		return clientMessage.getMessage();

	}

	public void send(Message request) throws Exception {

		if (!isConnected)
			throw new ConnectFailedException();

		try {
			out.writeMessage(request);
		} catch (Exception e) {
			e.printStackTrace();
			throw new ConnectFailedException("Error writing to stream");
		}

	}

	public Message awaitResponse() throws Exception {

		if (!isConnected)
			throw new ConnectFailedException();

		ClientMessage clientMessage = clientMessageQueue.poll(200, TimeUnit.DAYS);
		clientMessageQueue.clear();

		if (clientMessage == null) {
			throw new ConnectFailedException();
		}
		if (clientMessage == null || clientMessage.isEmpty()) {

			throw new ConnectFailedException();
		} else if (clientMessage.isRejected()) {

			Reject reject = ServerResponseMessageParser.parseReject(clientMessage.getMessage());
			throw new Exception(reject.getText());
		}

		return clientMessage.getMessage();

	}

	private void sendHeartbeat(String testReqId) {
		Message logoutMsg = ClientRequestMessageFactory.createHeartbeat(templateRegistry, nextOutboundMsgSequenceNumber(), testReqId);
		out.writeMessage(logoutMsg);
	}

	public TemplateRegistry getTemplateRegistry() throws Exception {
		if (templateRegistry == null) {
			throw new ConnectFailedException();
		}
		return templateRegistry;
	}

	private FastMessageType getMessageType(Message msg) {
		String msgTypeCode = msg.getString("MessageType");
		FastMessageType fastMsgType = FastMessageType.getFastMessageType(msgTypeCode);
		return fastMsgType;
	}

	private class StreamWoker implements Runnable {

		private ArrayCache batchValues[];
		private ArrayCache batchValuesTime[];
		private int[] dimensions;
		private MetricUpdateCallback streamRefreshCallback = null;
		private SimpleMetricUpdateCallback streamRefreshCallbackPureData = null;

		public StreamWoker(ArrayCache[] batchValues, ArrayCache[] batchValuesTime, int dimensions[], MetricUpdateCallback streamRefreshCallback,
				SimpleMetricUpdateCallback streamRefreshCallbackPureData) {
			this.batchValues = batchValues;
			this.batchValuesTime = batchValuesTime;
			this.dimensions = dimensions;
			this.streamRefreshCallback = streamRefreshCallback;
			this.streamRefreshCallbackPureData = streamRefreshCallbackPureData;
		}

		@Override
		public void run() {

			Console.writeln("Start stream data");
			isStreamRuning.set(true);
			String stopReason = "terminated by user";
			while (isStreamEnabled.get() && !Thread.interrupted()) {
				try {

					Message responseMsg = awaitResponse();

					FastMessageType responseMessageType = getMessageType(responseMsg);
					if (responseMessageType == FastMessageType.LOGOUT) {
						clientMessageQueue.offer(new ClientMessage(responseMsg));

						break;
					}

					TransmitDataRequest response = ServerResponseMessageParser.parseTransmitDataRequest(responseMsg);

					if (response.getMsgType().contains("OK")) {

						// Gson gson = new Gson();
						// Type mapType = new
						// TypeToken<CalculationStatusMessage>() {
						// }.getType();

						// CalculationStatusMessage statusMessg =
						// gson.fromJson(response.getMsgType(), mapType);

						float[] data = response.getDataFloat();
						long[] time = response.getTime();

						if (data.length == 0)
							continue;

						for (int k = 0; k < batchValues.length; k++) {
							batchValuesTime[k].lockToWrite();
							batchValues[k].lockToWrite();
						}

						int len = 0;

						// while (len < data.length)
						for (int t = 0; t < time.length; t++) {
							for (int k = 0; k < dimensions.length; k++) {

//								boolean flag = true;
//								for (int m = 0; m < dimensions[k]; m++) {
//									flag = flag && Double.isNaN(data[len + m]);
//								}
//
//								if (flag){
//									len+=dimensions[k];
//									continue;
//								}

								for (int m = 0; m < dimensions[k]; m++) {

									batchValues[k].writeNextDouble(data[len]);

									len++;
								}
								batchValuesTime[k].writeNextLong(time[t]);

							}

						}

						// for (int k = 0; k < batchValues.length; k++)
						// batchValuesTime[k].writeNextLong(time);

						for (int k = 0; k < batchValues.length; k++) {
							batchValuesTime[k].unlockToWrite();
							batchValues[k].unlockToWrite();
						}

						if (streamRefreshCallback != null) {

							List<MetricRefreshValue> refreshValue = new ArrayList<MetricRefreshValue>();
							len = 0;
							for (int i = 0; i < time.length; i++) {

								for (int j = 0; j < dimensions.length; j++)
									for (int k = 0; k < dimensions[j]; k++) {
										if (!Double.isNaN(data[len]) && batchMetricKeys != null)
											refreshValue.add(new MetricRefreshValue(batchMetricKeys.get(j), k, data[len], time[i]));
										len++;
									}

							}
							
							if(refreshValue.size()>0)
								streamRefreshCallback.onDataRefresh(refreshValue);
						}

						if (streamRefreshCallbackPureData != null && time.length>0)
							streamRefreshCallbackPureData.onDataRefresh(data, time);

						// {
						// System.out.println("=====new  data block========="+(new
						// Timestamp(System.currentTimeMillis()))+"=======================================");
						//
						// //System.out.println("data size=" + data.length +
						// "\t" + data[0] + "\t" + data[data.length - 1]);
						// System.out.println("time size=" + time.length + "\t"
						// + (new Timestamp(time[0])) + "\t" + (new
						// Timestamp(time[time.length - 1])));
						//
						// //System.out.println("=============================");
						// }

						if (response.getMsgType().contains("STOP")) {
							stopReason = "terminated by server";
							break;
						}
					} else {
						stopReason = response.getMsgBody();
						break;

					}

				} catch (IOException e) {
					stopReason = "Error - " + e.getMessage();
					break;
				} catch (Exception e) {
					e.printStackTrace();
					stopReason = "Error - " + e.getMessage();
					break;
				}
			}

			Console.writeln("Stop stream data: " + stopReason);
			isStreamEnabled.set(false);
			isStreamRuning.set(false);

		}

	}

	private class InboundMessageWorker implements Runnable {
		public void run() {
			try {
				// Started inbound message logger thread
				while (true) {

					Message msg = in.readMessage();

					if (msg == null) {
						// End of input stream. Exiting from inbound message
						// worker thread
						break;
					}

					FastMessageType responseMessageType = getMessageType(msg);

					serviceMessageQueue.offer(new ServiceMessage(System.currentTimeMillis()));

					switch (responseMessageType) {
					case TEST_REQUEST:
						TestRequest testRequest = ServerResponseMessageParser.parseTestRequest(msg);
						sendHeartbeat(testRequest.getTestReqID());
						break;
					case HEARTBEAT:
						break;
					case LOGON:
						isLoggedOn = true;
						clientMessageQueue.offer(new ClientMessage(msg));
						break;
					case LOGOUT:
						isLoggedOn = false;
						clientMessageQueue.offer(new ClientMessage(msg));
						break;
					case REJECT:
						clientMessageQueue.offer(new ClientMessage(msg, true, false));
						break;
					default:
						clientMessageQueue.offer(new ClientMessage(msg));
						break;
					}

					if (isMessageLoggingEnabled)
						System.out.println("Recieved message: " + msg.toString());
				}
			} catch (FastException e) {
				isLoggedOn = false;
				isConnected = false;
				// Connection was terminated
			}
		}
	}

	private class HeartbeatMonitor implements Runnable {

		@Override
		public void run() {

			while (!Thread.interrupted()) {
				try {
					ServiceMessage serviceMessage = serviceMessageQueue.poll(SERVICE_TIMEOUT_SEC, TimeUnit.SECONDS);

					if (serviceMessage == null) {
						clientMessageQueue.offer(new ClientMessage(null, false, true));

					} else if (serviceMessage.isTerminated()) {
						break;
					} else {
					}

				} catch (Exception e) {

					break;
				}
			}
		}

	}

	public String getApiKey() {
		return apiKey;
	}

	public void setApiKey(String apiKey) {
		this.apiKey = apiKey;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getTemplatesFileName() {
		return templatesFileName;
	}

	public void setTemplatesFileName(String templatesFileName) {
		this.templatesFileName = templatesFileName;
	}

	public int getPort() {
		return port;
	}

	public void setPort(int port) {
		this.port = port;
	}

	public String getHost() {
		return host;
	}

	public void setMessageLoggingEnabled(boolean isMessageLoggingEnabled) {
		this.isMessageLoggingEnabled = isMessageLoggingEnabled;
	}

	public boolean isMessageLoggingEnabled() {
		return isMessageLoggingEnabled;
	}

	public boolean isLoggedOn() {
		return isLoggedOn;
	}

	public boolean isConnected() {
		return isConnected;
	}

	public int nextOutboundMsgSequenceNumber() {
		return outboundMsgSeqNum++;
	}

	public int getOutboundMsgSequenceNumber() {
		return outboundMsgSeqNum++;
	}

	public boolean isDebugModeEnabled() {
		return debugModeEnabled;
	}

	public void setDebugModeEnabled(boolean debugModeEnabled) {
		this.debugModeEnabled = debugModeEnabled;
	}

	public String getStatus() {
		return callStatus.toString();
	}

	@Override
	protected void finalize() throws Throwable {

		this.stop();

		super.finalize();
	}

	public void setStreamRefreshCallback(MetricUpdateCallback streamRefreshCallback) {
		this.streamRefreshCallback = streamRefreshCallback;
	}

	public SimpleMetricUpdateCallback getStreamRefreshCallbackPureData() {
		return streamRefreshCallbackPureData;
	}

	public void setStreamRefreshCallbackPureData(SimpleMetricUpdateCallback streamRefreshCallbackPureData) {
		this.streamRefreshCallbackPureData = streamRefreshCallbackPureData;
	}
	
	public AtomicBoolean isStreamEnabled() {
		return isStreamEnabled;
	}

	public int getRestarTimeWait() {
		return restarTimeWait;
	}

	public void setRestarTimeWait(int restarTimeWait) {
		this.restarTimeWait = restarTimeWait;
	}

}