/*
 ###############################################################################
 #                                                                             #
 #    Copyright (C) 2011-2016 OpenMEAP, Inc.                                   #
 #    Credits to Jonathan Schang & Rob Thacher                                 #
 #                                                                             #
 #    Released under the LGPLv3                                                #
 #                                                                             #
 #    OpenMEAP is free software: you can redistribute it and/or modify         #
 #    it under the terms of the GNU Lesser General Public License as published #
 #    by the Free Software Foundation, either version 3 of the License, or     #
 #    (at your option) any later version.                                      #
 #                                                                             #
 #    OpenMEAP 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 Lesser General Public License for more details.                      #
 #                                                                             #
 #    You should have received a copy of the GNU Lesser General Public License #
 #    along with OpenMEAP.  If not, see <http://www.gnu.org/licenses/>.        #
 #                                                                             #
 ###############################################################################
 */

package com.openmeap.cluster;

import java.net.ConnectException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Vector;

import org.apache.commons.lang.exception.ExceptionUtils;
import com.openmeap.thirdparty.org.json.me.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.openmeap.cluster.dto.ClusterNodeRequest;
import com.openmeap.constants.FormConstants;
import com.openmeap.http.HttpRequestExecuter;
import com.openmeap.http.HttpResponse;
import com.openmeap.json.JSONObjectBuilder;
import com.openmeap.model.ModelManager;
import com.openmeap.model.dto.ClusterNode;
import com.openmeap.model.dto.GlobalSettings;
import com.openmeap.services.dto.Result;
import com.openmeap.util.AuthTokenProvider;
import com.openmeap.util.Utils;

public class ClusterNodeHealthCheckThread implements Runnable {

	private Logger logger = LoggerFactory.getLogger(ClusterNodeHealthCheckThread.class);
	private HttpRequestExecuter httpRequestExecuter;
	private ModelManager modelManager;
	private GlobalSettings settings;
	private Integer restartInterval = 30000;
	private Integer checkInterval = 2000;
	private Vector<Exception> lastCheckExceptions;

	@Override
	public void run() {
		while(true) {
			try {
				_run();
			} catch(Exception e) {
				logger.error("Health check thread threw an exception.  Trying to restart in 30 seconds.",e);
				try {
					Thread.sleep(restartInterval);
				} catch (InterruptedException e1) {
					logger.error("Nap interrupted!",e1);
				}
			}
		}
	}
		
	private void _run() {
		settings = modelManager.getGlobalSettings();
		JSONObjectBuilder builder = new JSONObjectBuilder();
		ClusterNodeRequest request = new ClusterNodeRequest();
		request.setSubject(ClusterNodeRequest.HEALTH_CHECK);
		lastCheckExceptions = new Vector<Exception>();
		while(true) {
			synchronized(this) {
				lastCheckExceptions.clear();
				if(settings.getClusterNodes()!=null) { 
					for(ClusterNode clusterNode : settings.getClusterNodes()) {
						try {
							request.setClusterNode(clusterNode);
							HttpResponse response = null;
							try {
								response = httpRequestExecuter.postContent(
										clusterNode.getServiceWebUrlPrefix()+"/service-management/?action="+ClusterNodeRequest.HEALTH_CHECK+"&auth="+AuthTokenProvider.newAuthToken(settings.getServiceManagementAuthSalt()), 
										builder.toJSON(request).toString(3), FormConstants.CONT_TYPE_JSON);
							} catch(Exception e) {
								logger.error(clusterNode.getServiceWebUrlPrefix()+" health check returned exception",e);
								Throwable t = ExceptionUtils.getRootCause(e);
								ClusterNode.Status err = null;
								if( t instanceof ConnectException ) {
									err = ClusterNode.Status.CONNECT_ERROR;
								} else {
									err = ClusterNode.Status.ERROR;
								}
								synchronized(clusterNode) {
									clusterNode.setLastStatus(err);
									clusterNode.setLastStatusMessage(t.getMessage());
									clusterNode.setLastStatusCheck(new Date());
								}
								if(response!=null && response.getResponseBody()!=null) {
									Utils.consumeInputStream(response.getResponseBody());
									response.getResponseBody().close();
								}
								continue;
							}
							if( response!=null && response.getStatusCode()==200 ) {
								String json = Utils.readInputStream(response.getResponseBody(), FormConstants.CHAR_ENC_DEFAULT);
								JSONObject jsonObj = new JSONObject(json);
								Result result = (Result) builder.fromJSON(jsonObj, new Result());
								response.getResponseBody().close();
								synchronized(clusterNode) {
									clusterNode.setLastStatus(
											result.getStatus()==Result.Status.SUCCESS
											? ClusterNode.Status.GOOD
											: ClusterNode.Status.ERROR);
									clusterNode.setLastStatusMessage(result.getMessage());
									clusterNode.setLastStatusCheck(new Date());
								}
							} else {
								synchronized(clusterNode) {
									clusterNode.setLastStatus(ClusterNode.Status.ERROR);
									String msg = "Service node "+clusterNode.getServiceWebUrlPrefix()+" returned a non-200 status code "+response.getStatusCode()
										+" "+Utils.readInputStream(response.getResponseBody(), FormConstants.CHAR_ENC_DEFAULT);
									logger.error(msg);
									clusterNode.setLastStatusMessage(msg);
									response.getResponseBody().close();
									clusterNode.setLastStatusCheck(new Date());
								}
							}
						} catch(Exception e) {
							logger.error("Exception performing health check",e);
							lastCheckExceptions.add(e);
						}
					}
				}
			}
			synchronized(lastCheckExceptions) {
				lastCheckExceptions.notifyAll();
			}
			try {
				Thread.sleep(checkInterval);
			} catch (InterruptedException e) {
				logger.error("Nap interrupted!",e);
			}
		}
	}
	
	public synchronized void refreshSettings() {
		modelManager.getModelService().clearPersistenceContext();
		settings = modelManager.getGlobalSettings();
	}
	
	public List<Exception> checkNowAndWait() throws InterruptedException {
		synchronized(lastCheckExceptions) {
			lastCheckExceptions.wait();
			return new ArrayList<Exception>(lastCheckExceptions);
		}
	}
	
	public GlobalSettings getSettings() {
		return settings;
	}

	public void setHttpRequestExecuter(HttpRequestExecuter httpRequestExecuter) {
		this.httpRequestExecuter = httpRequestExecuter;
	}

	public void setModelManager(ModelManager modelManager) {
		this.modelManager = modelManager;
	}

	public void setCheckInterval(Integer checkInterval) {
		this.checkInterval = checkInterval;
	}

	public void setRestartInterval(Integer restartInterval) {
		this.restartInterval = restartInterval;
	}

}
