package motherbugtweetntwitch.app;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.math.BigInteger;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpState;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.URIException;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.multipart.FilePart;
import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
import org.apache.commons.httpclient.methods.multipart.Part;
import org.apache.commons.httpclient.methods.multipart.StringPart;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.osgi.service.log.LogService;

import com.buglabs.application.IServiceProvider;
import com.buglabs.bug.base.pub.IBUGBaseControl;
import com.buglabs.bug.base.pub.ITimeProvider;
import com.buglabs.bug.base.pub.IToneGenerator;
import com.buglabs.bug.module.camera.pub.ICameraDevice;
import com.buglabs.bug.module.motion.pub.IMotionObserver;
import com.buglabs.bug.module.motion.pub.IMotionSubject;
import org.apache.commons.codec.binary.Base64;
import com.buglabs.device.ButtonEvent;
import com.buglabs.device.IButtonEventListener;
import com.buglabs.device.IButtonEventProvider;
import com.buglabs.services.ws.PublicWSAdmin;
/**
 * 
 * @author jconnolly
 * 
 * This is the main application that does most of the grunt work. 
 * I hope to make it more modular with the next version.
 *
 */
public class MotherBUGTweetNTwitchApp implements IButtonEventListener,
		IMotionObserver {

	
	//basic config vars fill in the twitxr/twitter vars with your login info
	//go to http://twitxr.com
	//go to http://twitter.com 
	//register your account and put your registration info into the vars below
	private final String TWITXR_LOGIN = " " ;
	private final String TWITXR_PASSWD = " ";

	private final String TWITTER_LOGIN = " ";
	private final String TWITTER_PASSWD = " ";
	
	
	private final boolean DEBUG = true;
	private final String PATH = "/tmp/tempimage.jpeg";
	
	//variable in seconds to prevent rapid-fire tweets/twitches.
	//change this to lower # (>0) to allow for more frequent twitters about motion events
	private final int MOTION_DETECT_DELAY = 3600;
	
	
	//handles to services
	private ICameraDevice cam;
	private IButtonEventProvider buttonProv;
	private LogService log;
	private IMotionSubject motion;
	//eventually want to use these to make it more remotely customizable
	private IBUGBaseControl baseControl;
	private IToneGenerator toneGen;
	private PublicWSAdmin wsAdmin;
	private ITimeProvider timeProv;
	
	//press button to go into motion detection mode
	private boolean detectionMode;
	//press holds the value (in ms) for last time motion was detected
	private long lastMotionTime;
	
	///times to begin tweeting/twitching motion.  In 24-hour format 
	private String startHour = "22";
	private String endHour = "13";

	
	//sets up the handles to services and registers the appropriate listeners
	public MotherBUGTweetNTwitchApp(IServiceProvider serviceProv) {
		baseControl = (IBUGBaseControl) serviceProv
				.getService(IBUGBaseControl.class);
		toneGen = (IToneGenerator) serviceProv.getService(IToneGenerator.class);
		cam = (ICameraDevice) serviceProv.getService(ICameraDevice.class);
		buttonProv = (IButtonEventProvider) serviceProv
				.getService(IButtonEventProvider.class);
		wsAdmin = (PublicWSAdmin) serviceProv.getService(PublicWSAdmin.class);
		motion = (IMotionSubject) serviceProv.getService(IMotionSubject.class);
		timeProv = (ITimeProvider) serviceProv.getService(ITimeProvider.class);

		log = (LogService) serviceProv.getService(LogService.class);

		motion.register(this);
		buttonProv.addListener(this);
		
		//Change to TRUE if you want app to start detecting motion without having to touch a button
		detectionMode = true;

		lastMotionTime = timeProv.getTime().getTime();

	}

	// wait for button event to begin detecting motion
	public void buttonEvent(ButtonEvent event) {
		detectionMode = true;
		log.log(LogService.LOG_DEBUG, "MotherBUGTweetNTwitch: Button pressed, begin detecting motion.");

	}

	// checks to see if button was presSearches across multiple fields will be executed with the '&' operator sed to begin detecting motion
	//
	public void motionDetected() {

		if (!detectionMode) {
			log
					.log(LogService.LOG_DEBUG,
							"MotherBUGTweetNTwitch: Motion detected, but button not pressed to engaged begin waiting.");
			return;
		}

		if ((getTimeElapsed() < this.MOTION_DETECT_DELAY)) {
			log
					.log(LogService.LOG_DEBUG,
							"MotherBUGTweetNTwitch: Motion detected, but not enough time elapsed to do anything.");
			return;
		}

		if (!(timeInRange())) {
			log
					.log(LogService.LOG_DEBUG,
							"MotherBUGTweetNTwitch: Motion detected, but not in range of time to tweet and twitch");
			return;
		}

		tweet();
		writeImageToFile();
		postImageToTwitxr();

	}

	// returns true if current hour is before (or equal to) endHour, or after (or equal to) beginHour 
	private boolean timeInRange() {
		Date now = timeProv.getTime();

		SimpleDateFormat formatter = new SimpleDateFormat("HH");
		String hour = formatter.format(now);
		log.log(LogService.LOG_DEBUG, "MotherBUGTweetNTwitch: The hour is " + hour + " and the start hour is "
				+ startHour + " and end hour is " + endHour);
		return (Integer.parseInt(hour) <= Integer.parseInt(endHour) || Integer
				.parseInt(hour) >= Integer.parseInt(startHour));

	}

	private int getTimeElapsed() {

		long now = timeProv.getTime().getTime();
		int timeDiffInSeconds = (int) ((now - lastMotionTime) / 1000);
		lastMotionTime = now;
		log.log(log.LOG_DEBUG, "MotherBUGTweetNTwitch: Time between last motion detection and now: "
				+ timeDiffInSeconds);
		return timeDiffInSeconds;

	}

	public void tweet() {

		String userPass = new String(Base64.encodeBase64((this.TWITTER_LOGIN
				+ ":" + this.TWITTER_PASSWD).getBytes()));

		HttpURLConnection c = null;
		InputStream input;
		BufferedReader dataInput;
		StringBuffer buffer = null;
		String line;
		URL u;

		try {
			u = new URL("http://twitter.com/statuses/update.xml");
		} catch (MalformedURLException e) {
			System.err.print(e.getMessage());
			log.log(LogService.LOG_ERROR, e.getMessage());
			return;
		}

		int responseCode = -1;

		try {
			c = (HttpURLConnection) u.openConnection();
			c.setRequestProperty("Authorization", "Basic " + userPass);
			c.setDoOutput(true);
			c.setRequestMethod("POST");
			OutputStream out = c.getOutputStream();
			// Form the POST parameters
			String content = "";
			content += "status="
					+ URLEncoder
							.encode(
									"(Random key to fool twitter "
											+ Math.random()

											+ ") Motion detected on the "+TWITTER_LOGIN+" BUG! Check http://twitxr.com/"+TWITXR_LOGIN+" for the image",
									"UTF-8");

			out.write(content.toString().getBytes("UTF-8"));
			out.close();

			responseCode = c.getResponseCode();
		} catch (IOException e) {
			System.err.println(e.getMessage());
		}

		if (responseCode != HttpURLConnection.HTTP_OK) {
			if (responseCode == 401) {
				log
						.log(LogService.LOG_DEBUG,
								"MotherBUGTweetNTwitch: Incorrect Twitter username or password");
			}
			return;
		}

		try {
			buffer = new StringBuffer();
			input = c.getInputStream();
			dataInput = new BufferedReader(new InputStreamReader(input));

			while ((line = dataInput.readLine()) != null) {
				buffer.append(line);
				buffer.append('\n');
			}
		} catch (Exception e) {
			System.err.println(e.getMessage());
		}
		String response = buffer.toString();
		log.log(LogService.LOG_DEBUG, "MotherBUGTweetNTwitch: Logged into Twitter as " + this.TWITTER_LOGIN);
		log.log(LogService.LOG_DEBUG, "MotherBUGTweetNTwitch: Received response from Twitter server: "
				+ response);
		return;
	}

	public void postImageToTwitxr() {
		if (DEBUG) {
			
			log.log(LogService.LOG_DEBUG, "MotherBUGTweetNTwitch: Trying to post...");
		}
		HttpClient client = new HttpClient();
		client.getParams().setParameter("http.useragent", "BUG");
		client.getParams().setAuthenticationPreemptive(true);
		HostConfiguration host = client.getHostConfiguration();

		try {
			host.setHost(new URI("http://twitxr.com", true));
		} catch (URIException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		} catch (NullPointerException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}

		Credentials credentials;
		try {
			credentials = new UsernamePasswordCredentials(this.TWITXR_LOGIN,
					toMD5(this.TWITXR_PASSWD));

			AuthScope authScope = new AuthScope(AuthScope.ANY_HOST,
					AuthScope.ANY_PORT);
			HttpState state = client.getState();
			state.setCredentials(authScope, credentials);
		} catch (NoSuchAlgorithmException e1) {

			e1.printStackTrace();
		}

		PostMethod post = new PostMethod("/api/rest/postUpdate");

		post.getParams().setBooleanParameter(
				HttpMethodParams.USE_EXPECT_CONTINUE, true);

		try {

			if (DEBUG) {
				log.log(LogService.LOG_DEBUG, " MotherBUGTweetNTwitch: Uploading " + this.PATH + " to "
						+ host.getHost());
			}

			Part[] parts = {
					new FilePart("image", new File(this.PATH)),

					new StringPart("text", "Motion detected! http://twitter.com/"+TWITTER_LOGIN),
					new StringPart("source", "le BUG"),
					new StringPart("place", "915 Broadway, New York, NY, 10010") };

			post.setRequestEntity(new MultipartRequestEntity(parts, post
					.getParams()));

			client.getHttpConnectionManager().getParams().setConnectionTimeout(
					5000);

			int status = client.executeMethod(host, post);
			System.err.println(post.getStatusLine());
			System.err.println(post.getResponseBodyAsString());
			if (status == HttpStatus.SC_OK) {
				log.log(LogService.LOG_DEBUG, "MotherBUGTweetNTwitch: Upload complete, response:\n"
						+ post.getResponseBodyAsString());
			} else {
				log.log(LogService.LOG_DEBUG, "MotherBUGTweetNTwitch: Upload failed, response:\n"
						+ HttpStatus.getStatusText(status));
				System.err.println("Upload failed, response:\n"
						+ HttpStatus.getStatusText(status));
			}
		} catch (Exception e) {
			log.log(LogService.LOG_ERROR, "MotherBUGTweetNTwitch: Error: " + e.getMessage());

			System.err.println("MotherBUGTweetNTwitch: Error: " + e.getMessage());
			e.printStackTrace();
		} finally {
			post.releaseConnection();
		}

	}

	// helpers
	public static String toMD5(String s) throws NoSuchAlgorithmException {
		MessageDigest m = MessageDigest.getInstance("MD5");
		m.update(s.getBytes(), 0, s.length());
		return (new BigInteger(1, m.digest()).toString(16));
	}

	public void writeImageToFile() {
		if (DEBUG) {
			System.out.println("Opening " + this.PATH + " to write");
			log.log(log.LOG_DEBUG, "Opening " + this.PATH + " to write");
		}
		File temp = new File(this.PATH);
		FileOutputStream fos;
		try {
			fos = new FileOutputStream(temp);
			if (DEBUG) {
				System.out.println("Writing " + this.PATH);
				log.log(log.LOG_DEBUG, "Writing " + this.PATH);
			}
			fos.write(cam.getImage());
			fos.close();
			if (DEBUG) {
				System.out.println("Wrote " + this.PATH);
				log.log(log.LOG_DEBUG, "Wrote " + this.PATH);
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			log.log(log.LOG_ERROR, e.getMessage());
			e.printStackTrace();
		}

	}

}