/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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
 *
 *      http://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 examples.telnet;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.apache.commons.net.telnet.*;
import org.apache.commons.net.io.Util;

import java.util.Date;
import java.sql.*;

/***
 * This is a based on an example of
 *  a trivial use of the TelnetClient class,
 * <p>
 ***/

// This class requires the IOUtil support class!
public final class WScn
{

	OutputStream ostream;
	InputStream istream;
	TelnetClient tclient;
	
	int newSamples = 0;

	WScn(String hostname, int port) {
		tclient = new TelnetClient();
		SuppressGAOptionHandler gah = new SuppressGAOptionHandler(false,false,true,true);
		EchoOptionHandler eh = new EchoOptionHandler(false,false,true,true);
		tclient.setDefaultTimeout(10000);
		System.out.println("Will connect to "+hostname+":"+port);
		System.out.println(" Network Timeout is "+tclient.getDefaultTimeout()+" ms.");
		try {
		    tclient.addOptionHandler(gah);
			tclient.addOptionHandler(eh);
			tclient.connect(hostname, port);
			System.out.println("Connected.");
		}
		catch (Exception e)
		{
			e.printStackTrace();
			System.exit(1);
		}
		istream = tclient.getInputStream();
		ostream = tclient.getOutputStream();
	}

	public final static String charCodeDisplay (int ccode) {
		if ((ccode>=32) && (ccode<127)) {
			  return " " + new String(Character.toChars(ccode));	
			} else {
			  return  " .";
			}
	}
	
	//
	// check if 2 timestamps are at maximum <maxDistms> milliseconds apart
	//
	public final static boolean timeInRange(Timestamp t1, Timestamp t2,
			                                long maxDistms) {
		long t1ms = t1.getTime();
		long t2ms = t2.getTime();
		return (Math.abs(t2ms-t1ms)<=maxDistms);
		
	}
			                                 
	//
	// send one byte to the remote IPWE1 server
	// to cover a flaw in IPWE1, a delay is necessary after the byte
	// to guarantee that only one byte per packet is received by IPWE1
	//
	public final void xmit(int cod, OutputStream ostr) {
		try {
			this.ostream.write(cod);
			if (ostr != null) {
				ostr.write(cod);
			}
			this.ostream.flush();
			Thread.sleep(100);
			} catch (Exception e) {
				System.out.println("Error in xmit: "+e);
				System.exit(1);
			}
	}
	//
	// receive one byte from the remote server
	//
	public final int recv(String inf) {
		int b=0;
		try {
		b = this.istream.read();
		} catch (Exception e) {
			System.out.println("error in recv: "+e);
			System.exit(1);
		}
		if (inf!=null) {
		  System.out.println(String.format(inf+": got 0x%02X - ",b)+charCodeDisplay(b));
		}
		return b;
	}
	//
	// receive one byte from the remote server,
	// no special log prefix info given
	//
	public final int recv() {
		return recv("recv");
	}
	//
	// send a string to the remote IPWE1 server
	// 
	public final void xmit(String xstr, OutputStream ostr) {
		int p;
		int l = xstr.length();
		for (p=0;p<l;p++) {
			this.xmit(xstr.codePointAt(p), ostr);
		}
	}
	

	public final void waitFor(String waitstr, OutputStream ostr, String inf) {
		boolean waiting =true;
		int pos = 0;
		int c = 0;
		while (waiting) {
			c = waitstr.codePointAt(pos);
			if (inf!=null) {
			  System.out.println(" Waiting for: "+charCodeDisplay(c));
			}
			try {
				c = this.recv(inf);
				if (ostr!=null) {
					ostr.write(c);
				}
			} catch (Exception e) {
				System.out.println("Error in waitfor: "+e);
				System.exit(1);
			}
			if (c==waitstr.codePointAt(pos)) {
				pos++;
				if (pos>=waitstr.length())
					return;
			}
			else {
				pos = 0;
			}
		}
	}
	
	//
	// wait for string waitstr, prevent logging
	//
	public final void waitFor(String waitstr, OutputStream ostr) {
		this.waitFor(waitstr, ostr, null);
	}
	
	public final void disconnect() {
		try {
			this.tclient.disconnect();
		} catch (Exception e) {
			System.out.println("Error in disconnect: "+e);
			System.exit(1);
		}
	}
	
	//
	// read the values of one temperature/humidity sensor <sensNum> attached to the IPWE1
	// and store them in the database using the prepared statement <st>
	//
	// @param number of sensor as known by the IPWE1
	// @param prepared statement to insert value into database 
	//
	public final void readSensor(int sensNum, PreparedStatement st) throws Exception {
		int lsb = 0;
		int msb = 0;
		this.xmit("S", null);
		this.waitFor("S", null,"readSensorWaitS");  // consume echo "S"
		this.xmit(sensNum, null);
		this.xmit("\r\n", null);
		lsb = this.recv("rx_cons_sensnum");        // consume echo sensor number byte
		lsb = this.recv("rx_cons_dummy");          // consume excess byte (don't know what)
		lsb = this.recv("rx_cons_sensid");         // consume result sensor number byte
		int temp = 0;
		int rh = 0;
		msb = this.recv("rx_temp_msb");
		lsb = this.recv("rx_temp_lsb");
		rh = this.recv("rx_rh");
		java.util.Date today = new java.util.Date();
		Timestamp rdt = new java.sql.Timestamp(today.getTime());   // store current time as time of reading
		temp = 256*msb+lsb;
		System.out.println("Sensor "+sensNum+": Temp="+temp+", Humid="+rh);
		// this.xmit("\r\n",null);
		this.waitFor("IPWE1> ", null,"readSensorWaitPrompt");
		if (st!=null) {
			st.setTimestamp(1, rdt);
			st.setInt(2, sensNum);
			st.setString(3, "TEMPDC");
			st.setFloat(4, (float) (temp/10.0));
			st.execute();                           // store temperature
			System.out.println("wrote temperature");
			st.setString(3, "RHUMID");
			st.setDouble(4, rh);
			st.execute();                           // store humidity
			System.out.println("wrote humidity");
		}
	}
	
	//
	// check if entry in SENSREAD is already there
	//
	public final boolean readAlreadyThere(Connection conn, int sensNum, String unit, Timestamp rtime, double val) {
		try {
			Timestamp lt =  new Timestamp(rtime.getTime()-30000);
			Timestamp ut = new Timestamp(rtime.getTime()+30000);
			Double lv = val-1.0;
			Double uv = val+1.0;
			PreparedStatement st = conn.prepareStatement("select READTIME, SENSOR, UNIT, VALUE "+
	                                                 "from SENSREAD where "+
				                                     " READTIME between ? and ? "+
	                                                 " and SENSOR=? and UNIT=? and VALUE between ? and ?");
		  st.setTimestamp(1, lt);
		  st.setTimestamp(2, ut);
		  st.setInt(3, sensNum);
		  st.setString(4, unit);
		  st.setDouble(5,lv);
		  st.setDouble(6,uv);
		  System.out.println("readAlreadyThere: check for "+unit+" on "+sensNum+
				             " between "+lt+".."+ut+" value between "+lv+".."+uv);
		  ResultSet rs = st.executeQuery();
     	   // System.out.println("readAlreadyThere: query executed");
     	   String tim;
     	   String uni;
     	   int sen;
    	   while (rs.next()) {
    	            tim = rs.getString("READTIME");
    	            uni = rs.getString("UNIT");
    	            sen = rs.getInt("SENSOR");
    	            val = rs.getFloat("VALUE");
    				System.out.println("readAlreadyThere: found: Time: "+tim+", Sensor: "+sen+", "+uni+": "+val);
    				return true;
    	   }
		} catch (Exception e) {
			System.out.println("error in readAlreadyThere: "+e);
		}
		System.out.println("readAlreadyThere: nothing found");
		return false;
	}
	//
	// read sensor <sensNum>'s history
	//
	public final void readSensorHistory(int sensNum, Connection conn ) {
		PreparedStatement st;
		try {
		  st = conn.prepareStatement(
    			"INSERT into SENSREAD "+
    			"(READTIME,SENSOR,UNIT,VALUE) "+
    			"VALUES(?,?,?,?)");
		
		int lsb = 0;
		int msb = 0;
		this.xmit("H", null);
		this.waitFor("H", null);  // consume echo "H"
		this.xmit(sensNum, null);
		this.xmit("\r\n", null);
		lsb = this.recv(null);        // consume echo sensor number byte
		lsb = this.recv(null);          // consume excess byte (don't know what)
		lsb = this.recv(null);         // consume result sensor number byte
		int sn=0;
		int temp = 0;
		int rh = 0;
		int eltim = 0;
		double tempdc = 0.0;
		double eltimM;
		int xr;
		String eltimMS;
		Timestamp now;
		Timestamp rt;
		java.util.Date today;
		for (sn=1;sn<=5;sn++) {
	      lsb = this.recv(null);          // consume excess byte (don't know what)
		  lsb = this.recv(null);          // consume excess byte (don't know what)
		  msb = this.recv(null);          // get elapsed time (in seconds?) MSB
		  lsb = this.recv(null);          // get elapsed time (in seconds?) LSB
		  eltim = msb*256+lsb;
		  eltimM = Math.floor(eltim/60);
		  eltimMS = eltimM+" min "+(eltim-(eltimM*60))+" sek.";
		  msb = this.recv(null);          // get temperature MSB
		  lsb = this.recv(null);          // get temperature LSB
		  temp = 256*msb+lsb;
		  tempdc = temp/10.0;
		  rh = this.recv(null);           // get relative humidity
		  today = new java.util.Date();
		  now = new java.sql.Timestamp(today.getTime());
		  rt = new Timestamp(now.getTime()-eltim*1000);
		  System.out.println(now + ": Sensor "+sensNum+" history entry "+sn+": temp="+temp/10.0+",humid="+rh+
				  ", elapsed_time="+eltimMS+" sent at "+rt);


		  if (!this.readAlreadyThere(conn,sensNum,"TEMPDC",rt,tempdc)) {
			st.setTimestamp(1,rt);
			st.setInt(2,sensNum);
			st.setString(3,"TEMPDC");
			st.setDouble(4,tempdc);
			xr = st.executeUpdate();
			System.out.println("readSensorHistory: "+xr+" rows inserted of TEMPC");
			this.newSamples += xr;
		  }
		  if  (!this.readAlreadyThere(conn,sensNum,"RHUMID",rt,rh)) {
				st.setTimestamp(1,rt);
				st.setInt(2,sensNum);
				st.setString(3,"RHUMID");
				st.setDouble(4,rh);
				xr = st.executeUpdate();
				System.out.println("readSensorHistory: "+xr+" rows inserted of RHUMID");
				this.newSamples += xr;
			  }
		}
		} catch (Exception e) {
			System.out.println("error in readSensorHistory: "+e);
			System.exit(1);
		}
		this.waitFor("IPWE1> ", null);
	}
	
	//
	// main method for testing 
	//
    public final static void main(String[] args)
    {
    	WScn w = new WScn("scherer3002.homelinux.net",40023);
    	
    	// log into this IPWE1 ...
    	//
    	w.waitFor("Username: ", System.out);
    	w.xmit("admin\r\n", System.out);
    	w.waitFor("Password: ", System.out);
    	w.xmit("8May2K051\r\n", System.out);
    	w.waitFor("IPWE1>", System.out);
    	//
    	// issue a "help" command for practice
    	//
    	// w.xmit("help\r\n", System.out);
    	// w.waitFor("IPWE1>", System.out);
    	//
    	// execute a "status" command to make sensor status human readable
    	//
    	// w.xmit("status\r\n", System.out);
    	// w.waitFor("IPWE1>", System.out);
    	 
    	Connection conn = null;
    	try {
    	  conn = DriverManager.getConnection(
    			            "jdbc:mysql://scherer3002.homelinux.net:43306/pucon","weatherscanner","8May2K051");
    	} catch (Exception e) {
    		System.out.println("error connecting to database: "+e);
    		System.exit(1);
    	}
    	
    	/*
    	// ResultSet  rs = null;
    	PreparedStatement st;
    	
        try {
        	// conn = DriverManager.getConnection("jdbc:mysql://ursubuntu:3306/pucon","root","8May2K051");
           	conn = DriverManager.getConnection("jdbc:mysql://scherer3002.homelinux.net:43306/pucon","weatherscanner","8May2K051");
        	System.out.println("got database connection");
        	    
        	st = conn.prepareStatement(
        			"INSERT into SENSREAD "+
        			"(READTIME,SENSOR,UNIT,VALUE) "+
        			"VALUES(?,?,?,?)");
    	//
    	// now read sensors 1 and 4
    	//
        System.out.println("Will read sensor 1");
    	w.readSensor(1,st);
        System.out.println("read Sensor 1, Will read sensor 4");
    	w.readSensor(4,st);
    	System.out.println("read sensor 4");
    	
        } catch (Exception e) {
        	System.out.println("Error reading and saving sensors: "+e);
        	System.exit(1);
        }
        //
        // show sensor 1's history
        //
        w.xmit("sensor 1\r\n", System.out);
        w.waitFor("IPWE1> ", System.out);
        */
    	
    	//
    	// read known sensor's history
    	//
        w.readSensorHistory(1,conn);
        w.readSensorHistory(4,conn);
        
    	//
    	// ... and log off again
    	//
        System.out.println("will exit");
        w.xmit("exit\r\n", System.out);    

        System.out.println("End: "+w.newSamples+" new values recorded");
        
        w.disconnect();
        
        System.out.println("Disconnected");
        
        //
        // for test purposes, read back all data stored in the sensors reading table
        //
        
        /* 
        int sens;
        float val;
        try {
        	System.out.println("Starting readback");
        	   Statement stmt = conn.createStatement();
        	   rs = stmt.executeQuery("select * from SENSREAD");
        	   System.out.println("query executed");
        	   while (rs.next()) {
        	            String tim = rs.getString("READTIME");
        	            String unit = rs.getString("UNIT");
        	            sens = rs.getInt("SENSOR");
        	            val = rs.getFloat("VALUE");
        				System.out.println("Time: "+tim+", Sensor: "+sens+", "+unit+": "+val);
        		}
        	 } catch (Exception e2) {
        	   System.out.println("Exception: "+e2);
        	   System.exit(1);
        	 }
        	 
        */

        System.exit(0);
        }
    }