/*******************************************************************************
 * 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.FILEDAO;

import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.HashSet;
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.DAO.ComponentData;
import alma.acs.monitoring.DAO.MonitorDAO;
import alma.acs.monitoring.blobber.BlobberWatchDog;
import alma.archive.tmcdb.persistence.TMCDBConfig;


@SuppressWarnings("deprecation")
public class FILEDAOImpl 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";


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

		TMCDBConfig config = TMCDBConfig.getInstance(log);

		log.info("This fileDAO 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 {
/*
	    // We skip monitoring information coming from simulated antennas
	    if ( simulatedAntennaHashSet.contains((inData.componentName.split("/"))[1]) ) {
		if ( log.isLoggable(Level.FINER) )
		    log.finer("Dropping blob data for " + inData.componentName.split("/")[1] + ":" + inData.propertyPathname());
		    return;
	    }
*/
	    try {
                // split clob into separate lines
	        PrintWriter dat = new PrintWriter(new BufferedWriter(new FileWriter(pathname+"/"+inData.componentName+"_"+inData.propertyName+".dat", true)));
	        String clob = new String(inData.getClob());
		StringBuffer s = new StringBuffer(clob.length());
		CharacterIterator it = new StringCharacterIterator(clob+'|');
		int count = 0;
		for (char ch = it.first(); ch != CharacterIterator.DONE; ch = it.next()) {
		  if (ch=='|') {
		    count++;
		    if ((count%2)==0)
		      s.append("\n");
		    else
		      s.append(" ");
		  } else {
		    s.append(ch);
		  }
		}
		dat.print(s.toString());
	        dat.close();

	        if (inData.statistics != null) {
	          PrintWriter stat = new PrintWriter(new BufferedWriter(new FileWriter(pathname+"/"+inData.componentName+"_"+inData.propertyName+".stat", true)));
	          stat.println("serialNumber="+inData.serialNumber+
	    		" componentName="+inData.componentName+
	    		" index="+inData.index+
	    		" propertyName="+inData.propertyName+
	    		" location="+location+
	    		" sampleSize="+inData.sampleSize+
	    		" startTime="+inData.startTime+
	    		" endTime="+inData.stopTime+
	    		" maxStat="+inData.statistics.max.doubleValue()+
	    		" minStat="+inData.statistics.min.doubleValue()+
	    		" meanStat="+inData.statistics.mean.doubleValue()+
	    		" stdDevStat="+inData.statistics.stdDev.doubleValue());
	          stat.close();
	        }

	        log.severe("Data written to directory " + pathname);
	    } catch (IOException e) {
	        log.severe("Cannot write to directory " + pathname);
	    }
	}

	public void close() {
		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.");
			}
		}
	}
}
