/*******************************************************************************
 * ALMA - Atacama Large Millimeter Array
 * Copyright (c) AUI - Associated Universities Inc., 2011
 * (in the framework of the ALMA collaboration).
 * All rights reserved.
 * 
 * This library 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 2.1 of the License, or (at your option) any later version.
 * 
 * This library 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 aInteger with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 *******************************************************************************/
package alma.archive.tmcdb.MySQLDAO;

import java.sql.*; 
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.lang.reflect.Constructor;
import java.lang.InterruptedException;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.io.IOException; 
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;

import alma.DAOErrType.wrappers.AcsJDBConnectionFailureEx;
import alma.JavaContainerError.wrappers.AcsJContainerServicesEx;
import alma.ACSErrTypeCommon.wrappers.AcsJCouldntCreateObjectEx;
import alma.acs.logging.AcsLogLevel;
import alma.acs.container.ContainerServices;
import alma.acs.concurrent.NamedThreadFactory;
import alma.acs.monitoring.MonitorPointTimeSeries;
import alma.acs.monitoring.MonitorPointValue;
import alma.acs.monitoring.DAO.ComponentData;
import alma.acs.monitoring.DAO.MonitorDAO;
import alma.acs.monitoring.blobber.BlobberWatchDog;
import alma.archive.tmcdb.persistence.TMCDBConfig;


@SuppressWarnings("deprecation")
public class MySQLDAOImpl implements MonitorDAO
{
	private ContainerServices containerServices;
	private Logger log;

	private LinkedBlockingQueue<ComponentData> myFileDataQueue;
	private final BlobConsumer myBlobConsumer;
	private final ExecutorService blobConsumerExecutor;
	//private Thread myBlobConsumerThread = null;
	private final HashSet<String> simulatedAntennaHashSet;

	// MQ attributes
	private String location;
	//private String pathname = "/home/database/blobber";
	private String pathname = "/home/ASTRI_SLNTCS/database/blobber";
	static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";  
	static final String DB_URL = "jdbc:mysql://localhost:3306/monitoring";
	//192.168.1001.121 == slntmcdb
	static final String USER = "ASTRI";
	static final String PASS = "ASTRIteam2014";
	private String sql;
	private Connection conn;
	private Statement stmt;

	public MySQLDAOImpl(ContainerServices cs, BlobberWatchDog watchDog) {
		this.containerServices = cs;
		this.log = cs.getLogger();

		TMCDBConfig config = TMCDBConfig.getInstance(log);
		
		try {
			//STEP 2: Register JDBC driver
//		log.info("Register JDBC...");
			Class.forName("com.mysql.jdbc.Driver");
			//STEP 3: Open a connection
			log.info("MySQLDAO connecting to database...");
			conn = DriverManager.getConnection(DB_URL,USER,PASS);
		} catch (ClassNotFoundException | SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		//		log.info("This mysqlDAO will use the following settings for storing data: "
		//			+ "pathname=" + pathname);

		HashSet<String> tmpSimulatedAntennaHashSet = config.getAntennaSimulatedSet(); // need this to allow declaring simulatedAntennaHashSet as final field
		if (tmpSimulatedAntennaHashSet == null) {
			simulatedAntennaHashSet = new HashSet<String>(1);
			simulatedAntennaHashSet.add("NONE");
			log.info("No simulated antennas on current deployment.");
		} else {
			simulatedAntennaHashSet = tmpSimulatedAntennaHashSet;
			for (Object simulatedAntennaName : simulatedAntennaHashSet) {
				log.info("Simulated antenna '" + (String) simulatedAntennaName + "' detected. No monitoring info coming from this antenna will be persisted to TMC");
			}
		}

		myFileDataQueue = new LinkedBlockingQueue<ComponentData>(100000);
		watchDog.addQueueToWatch(myFileDataQueue, "mq", 100000);

		//this.myBlobConsumer = new BlobConsumer();
		ThreadFactory tf = new NamedThreadFactory(containerServices.getThreadFactory(), "MQBlobConsumerThread");
		blobConsumerExecutor = Executors.newSingleThreadExecutor(tf);
		myBlobConsumer = new BlobConsumer();
		blobConsumerExecutor.execute(myBlobConsumer);
	}

	public void store(ComponentData inData) throws Exception {
		myFileDataQueue.put(inData);
	}

	/**
	 * Sends data over JMS to the TMCDumper
	 * <p>
	 * Really needed are <code>inData.startTime</code>, <code>inData.componentName</code>, 
	 * <code>inData.index</code>, <code>inData.clob</code>.
	 * <p>
	 * The consumer side code is under ADC/SW/TMCDB/TMC-WS.
	 */
	private void mqStore(ComponentData inData) throws Exception {


//		log.info("Data to MySQL DB ...");
		stmt = conn.createStatement();
		try{
			//STEP 3: Verify table existence
			DatabaseMetaData metadata = conn.getMetaData();
			ResultSet rstest = metadata.getTables(null, null, inData.componentName+"_"+inData.propertyName, null);

			if(!rstest.next()){
				stmt.executeUpdate("CREATE TABLE `"+inData.componentName+"_"+inData.propertyName+"` (`timetag` bigint(20) DEFAULT NULL,`value` CHAR(30) DEFAULT NULL, UNIQUE KEY `timetag_UNIQUE` (`timetag`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1;");
			}

			
			MonitorPointTimeSeries mpt = inData.getMonitorPointTimeSeries();
			List<MonitorPointValue> mptList = mpt.getDataList();
			int numPoints = mptList.size(); //TODO: Handle case of multi-valued Monitor Point?
			for (int i = 0; i < numPoints; i++) {
				Long mptTime = mptList.get(i).getTime();
				String value = mptList.get(i).getData().get(0).toString();
				if (numPoints > 1)
					log.fine("Time : " + mptTime + " Value: " + value);
				sql = "INSERT INTO " + inData.componentName + "_" + inData.propertyName + " VALUES(" + mptTime + ",'"
						+ value + "');";
				log.warning(sql.length() + " >>>" + sql + "<<<");
				stmt.executeUpdate(sql);
				//STEP 4: Execute a query
			}
			log.info("Data written to MySQL DB ");
	} catch(SQLException se){
			//Handle errors for JDBC
			log.severe("JDBC error "+se.getMessage());
		}catch(Exception e){
			//Handle errors for Class.forName
			log.severe("Database error "+e.getMessage()); 
		}//end try
	}

	public void close() {
//		log.severe("Close statement...");
		try {
			stmt.close();
			conn.close();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			log.severe("Error on DB close: "+e.getMessage());
		}
		myBlobConsumer.cancel();

		// after the above cancel() call, the loop should terminate itself. 
		// We wait up to 1000 ms, and log a warning if the loop takes longer or fails otherwise.
		blobConsumerExecutor.shutdown();
		boolean shutdownOK = false;
		try {
			shutdownOK = blobConsumerExecutor.awaitTermination(1000, TimeUnit.MILLISECONDS);
		} catch (InterruptedException ex) {
			// log below...
		}
		if (!shutdownOK) {
			log.warning("Failed to orderly shut down the blobConsumerExecutor within 1000 ms.");
		}
	}

	public void openTransactionStore(String transactionName) throws AcsJDBConnectionFailureEx {
		// Do nothing.
	}

	public void closeTransactionStore() throws AcsJDBConnectionFailureEx {
		// Do nothing.
	}


	/**
	 * This threadable class consumes the data queue.
	 */
	class BlobConsumer implements Runnable
	{
		protected volatile boolean shouldTerminate = false;

		public BlobConsumer() {
		}

		/**
		 * Sets a flag so that the run method will stop processing as soon as possible.
		 */
		public void cancel() {
			shouldTerminate = true;
		}

		public void run() {
			log.info("Starting MQ blob consumer thread.");
			Thread.currentThread().setName("MQBlobConsumerThread");
			long start=0;
			long end =0;
			while(!shouldTerminate) {
				if (myFileDataQueue.size() > 0) {
					ComponentData tempBlobData = null;
					start = System.currentTimeMillis();
					try {
						// @TODO (hso): the possibly blocking call to take() circumvents thread termination based on the shouldTerminate flag.
						// See MonitorDAOImpl.BlobConsumer#run() for the use of "poll".
						tempBlobData = myFileDataQueue.take();
					} catch (InterruptedException ex) {
						ex.printStackTrace();
					}
					try {
						mqStore(tempBlobData);
					} catch (Exception ex) {
						ex.printStackTrace();
					}
					end = System.currentTimeMillis();
					if ( log.isLoggable(Level.FINER) )
						log.finer(Thread.currentThread().getName()
								+ ":FILE consumer: data taken from the queue, queue size="
								+ myFileDataQueue.size() + " consume time=" + (end-start));
				} else {
					//Thread.yield();
					try {
						Thread.sleep(500);
					} catch (InterruptedException ex) {
						log.log(Level.WARNING, "Unexpected InterruptedException in thread " + Thread.currentThread().getName()
								+ ". Will terminate this thread.", ex);
						break; // end the run method
					}
				}
			}
			int queueSize = myFileDataQueue.size();
			if (queueSize > 0) {
				log.warning("Terminating thread '" + Thread.currentThread().getName() +
						"' while the queue still contains " + queueSize + " BlobData elements.");
			}
		}
	}
}
