/**
 * Copyright (c) 2015 Andrew Rapp. All rights reserved.
 *
 * This file is part of arduino-remote-uploader
 *
 * arduino-remote-uploader is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * arduino-remote-uploader 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with arduino-remote-uploader.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.rapplogic.aru.uploader.nordic;

import gnu.io.PortInUseException;
import gnu.io.SerialPortEvent;
import gnu.io.UnsupportedCommOperationException;

import java.io.IOException;
import java.text.ParseException;
import java.util.Map;
import java.util.TooManyListenersException;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.OptionBuilder;
import org.apache.log4j.Logger;

import com.google.common.collect.Maps;
import com.rapplogic.aru.uploader.CliOptions;
import com.rapplogic.aru.uploader.serial.SerialSketchUploader;

/**
 * Uploads sketch to Nordic via the NordicSerialToSPI Sketch
 * 
 * @author andrew
 *
 */
public class NordicSketchUploader extends SerialSketchUploader {
	
	final Logger log = Logger.getLogger(NordicSketchUploader.class);
	
	private StringBuilder stringBuilder = new StringBuilder();
	
	private final int NORDIC_PACKET_SIZE = 32;
	
	public NordicSketchUploader() {
		super();
	}

	protected void handleSerial(SerialPortEvent event) {
       switch (event.getEventType()) {
           case SerialPortEvent.DATA_AVAILABLE:
               byte[] readBuffer = new byte[32];
               
               try {
            	   int numBytes = getInputStream().read(readBuffer);
            	   
            	   for (int i = 0; i < numBytes; i++) {
            		   //System.out.println("read " + (char) readBuffer[i]);
 
            		   // don't add lf/cr chars
            		   if ((readBuffer[i] != 10 && readBuffer[i] != 13)) {
            			   stringBuilder.append((char) readBuffer[i]);                            
            		   }

            		   // got a new line
            		   if ((int)readBuffer[i] == 10) {    
            			   handleSerialReply(stringBuilder.toString());
            			   stringBuilder = new StringBuilder();
            		   }
            	   }
               } catch (Exception e) {
            	   log.error("Serial error", e);
               }

               break;
           }
	}
	
	protected void handleSerialReply(String reply) throws InterruptedException {
		// convert back to byte array for framework
		int[] replyArray = new int[] { MAGIC_BYTE1, MAGIC_BYTE2, 0, 0, 0};

		if (isVerbose()) {
			System.out.println("<-" + reply);				
		}
		
		// because we only have one serial port I've adopted a simple convention to use the serial
		// port for both debugging and control. Here's the convention:
		// starts with OK continue with net
		// starts with RETRY tx err/no ack, try again
		// starts with ERROR unrecoverable, fail
		// anything else is just debug
		
		if (reply.startsWith("OK")) {
			// parse id
			int replyId = Integer.parseInt(reply.split(",")[1].trim());
			
			replyArray[2] = OK;
			replyArray[3] = (replyId >> 8) & 0xff;
			replyArray[4] = replyId & 0xff;
		} else if (reply.startsWith("RETRY")) {
			// hacky
			replyArray[2] =	RETRY;
		} else if (reply.startsWith("ERROR")) {
			replyArray[2] = START_OVER;
		}
		
		// if not debug message, add reply to queue
		if (replyArray[2] != 0) {
			addReply(replyArray);
		}
	}
	
	@Override
	public void open(Map<String,Object> context) throws Exception {
		this.openSerial((String) context.get("device"), (Integer) context.get("speed"));
	}

	@Override
	public void close() throws Exception {
		getSerialPort().close();
	}
	
	// TODO send nordic address
	
	public void flash(String file, String device, int speed, boolean verbose, int ackTimeout, int arduinoTimeout, int retriesPerPacket, int delayBetweenRetriesMillis) throws IOException, PortInUseException, UnsupportedCommOperationException, TooManyListenersException, StartOverException {
		Map<String,Object> context = Maps.newHashMap();
		context.put("device", device);
		context.put("speed", speed);
		
		// determine max data we can send with each programming packet
		int pageSize = NORDIC_PACKET_SIZE - getProgramPageHeader(0, 0).length;
		super.process(file, pageSize, ackTimeout, arduinoTimeout, retriesPerPacket, delayBetweenRetriesMillis, verbose, context);
	}

	@Override
	protected void writeData(int[] data, Map<String, Object> context) throws Exception {
		//System.out.println("Writing packet " + toHex(data));
		for (int i = 0; i < data.length; i++) {
			write(data[i]);
		}
	}

	@Override
	protected String getName() {
		return "nRF24L01";
	}

	public final static String serialPort = "serial-port";
	public final static String baudRate = "baud-rate";
	
	private void runFromCmdLine(String[] args) throws org.apache.commons.cli.ParseException, IOException, PortInUseException, UnsupportedCommOperationException, TooManyListenersException, StartOverException {
		CliOptions cliOptions = getCliOptions();
		
		cliOptions.addOption(
				OptionBuilder
				.withLongOpt(serialPort)
				.hasArg()
				.isRequired(true)
				.withDescription("Serial port of of Arduino running NordicSPI2Serial sketch (e.g. /dev/tty.usbserial-A6005uRz). Required")
				.create("p"));

		cliOptions.addOption(
				OptionBuilder
				.withLongOpt(baudRate)
				.hasArg()
				.isRequired(true)
				.withType(Number.class)
				.withDescription("Baud rate of Arduino. Required")
				.create("b"));
		
		cliOptions.build();
		
		CommandLine commandLine = cliOptions.parse(args);

		if (commandLine != null) {
			new NordicSketchUploader().flash(
					commandLine.getOptionValue(CliOptions.sketch), 
					commandLine.getOptionValue(serialPort), 
					cliOptions.getIntegerOption(baudRate), 
					commandLine.hasOption(CliOptions.verboseArg),
					cliOptions.getIntegerOption(CliOptions.ackTimeoutMillisArg),
					cliOptions.getIntegerOption(CliOptions.arduinoTimeoutArg),
					cliOptions.getIntegerOption(CliOptions.retriesPerPacketArg),
					cliOptions.getIntegerOption(CliOptions.delayBetweenRetriesMillisArg));
		}
	}
	
	/**
	 * ex ceylon:arduino-remote-uploader-1.0-SNAPSHOT andrew$ ./nordic-uploader.sh --sketch /Users/andrew/Documents/dev/arduino-remote-uploader/resources/BlinkSlow.cpp.hex --serial-port /dev/tty.usbmodemfa131 --baud-rate 19200 arduino-timeout 0 --ack-timeout-ms 5000 --retries 50
Experimental:  JNI_OnLoad called.
Stable Library
=========================================
Native lib Version = RXTX-2.1-7
Java lib Version   = RXTX-2.1-7
Sending sketch to nRF24L01 radio, size 1102 bytes, md5 8e7a58576bdc732d3f9708dab9aea5b9, number of packets 43, and 26 bytes per packet, header ef,ac,10,a,4,4e,0,2b,1a,3c
...........................................
Successfully flashed remote Arduino in 3s, with 0 retries

	 * @param args
	 * @throws NumberFormatException
	 * @throws IOException
	 * @throws ParseException
	 * @throws org.apache.commons.cli.ParseException
	 * @throws PortInUseException
	 * @throws UnsupportedCommOperationException
	 * @throws TooManyListenersException
	 * @throws StartOverException
	 */
	public static void main(String[] args) throws NumberFormatException, IOException, ParseException, org.apache.commons.cli.ParseException, PortInUseException, UnsupportedCommOperationException, TooManyListenersException, StartOverException {		
		initLog4j();
		new NordicSketchUploader().runFromCmdLine(args);
//		new NordicSketchUploader().processNordic("/Users/andrew/Documents/dev/arduino-remote-uploader/resources/RAU-328-13k.hex", "/dev/tty.usbmodemfa131", Integer.parseInt("19200"), false, 5, 0, 50, 250);
	}
}