/*
 * Main.java
 *
 * Created on 16. Juli 2007, 19:02
 *
 * This is a simple Client for caiviar capiserver in fli4l
 *
 */

package caivar;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;

/**
 *
 * @author Christoph Fritsch <fli4l@dechristo.net>
 */
public final class Main {
    
    private static SimpleCapi capi = null;
    private static Options options = null;
    
    private static final int ERRORCODE = -1;
    
    private static final String RX_NUMBER2CALLFORMAT = "[+]?[\\d ]+";
    private static final String RX_DTMFFORMAT = "[\\d*#]+";
    private static final String RX_NUMBERFORMAT = "[\\d]+";
    
    private static final int DEFAULTTIMEOUT = 60;
    private static final int DEFAULTPORT = 7474;
    private static final String DEFAULTHOST = "127.0.0.1";
    private static final int MAXPORT = 65535;
    private static final int MAXREADOUTLENGTH = 65535;
    
    private static final int CALL = 0;
    private static final int FAX = CALL + 1;
    private static final int PLAY = FAX + 1;
    private static final int DTMF = PLAY + 1;
    private static final int READOUT = DTMF + 1;
    private static int iAction = -1;
    
    private static boolean debug = false;
    private static CommandLine line = null;
    
    private static String dtmf = null;
    private static String number2call = null;
    private static String action = null;
    private static int callTimeout;
    private static String filename;
    private static String leftOverArg0 = "";
    private static int serverPort;
    private static String serverAddress;
    private static InetAddress serverInetAddress;
    
    private static final ArrayList allowed_actions = new ArrayList();
    private static final ArrayList allowed_faxExtension = new ArrayList();
    private static final ArrayList allowed_playExtension = new ArrayList();
    
    private static File faxFile = null;
    private static File playFile = null;
    
    
    public static void main(String[] args)  {
	
	//specify allowed actions
	allowed_actions.add("call");
	allowed_actions.add("dtmf");
	allowed_actions.add("fax");
	allowed_actions.add("speak");
	allowed_actions.add("play");
	
	//sepcify possible files to fax
	allowed_faxExtension.add("html");
	allowed_faxExtension.add("ps");
	allowed_faxExtension.add("sff");
	
	//sepcify possible files to play
	allowed_playExtension.add("wav");
	allowed_playExtension.add("la");
	
	buildOptions();
	parseOptions(args);
	
	if(number2call.equalsIgnoreCase("")) {
	    log("Number2call not specified", true);
	    System.exit(ERRORCODE);
	}
	
	if(action.equalsIgnoreCase("call")) {
	    //requires number
	    iAction = CALL;
	} else if(action.equalsIgnoreCase("fax")) {
	    //requires number, file
	    iAction = FAX;
	    faxFile = new File(filename);
	    if(!validFile(faxFile, allowed_faxExtension)) {
		log("invalid file " + faxFile.getName() + " to fax", true);
		System.exit(ERRORCODE);
	    }
	} else if(action.equalsIgnoreCase("play")) {
	    //requires number
	    iAction = PLAY;
	    playFile = new File(filename);
	    if(!validFile(playFile, allowed_playExtension)) {
		log("invalid file " + playFile.getName() + " to play", true);
		System.exit(ERRORCODE);
	    }
	} else if(action.equalsIgnoreCase("dtmf")) {
	    //requires number, text
	    iAction = DTMF;
	    if(leftOverArg0.equals("") || !leftOverArg0.matches(RX_DTMFFORMAT)) {
		log("DTMF " + leftOverArg0 + " has invalid format", true);
		System.exit(ERRORCODE);
	    }
	} else if(action.equalsIgnoreCase("speak")) {
	    //requires number, text
	    iAction = READOUT;
	    if(leftOverArg0.equals("")) {
		log("You have to specify text to speak to callee", true);
		System.exit(ERRORCODE);
	    }
	}
	
	log("Connecting to "  + serverInetAddress.getHostName() +
		" (" + serverInetAddress.getHostAddress() + ") + Port: " + serverPort, false);
	
	try {
	    capi = new SimpleCapi(serverInetAddress.getHostName(), serverPort);
	    if(capi.call(number2call, callTimeout) == 0) {
		perform_action();
		
		while(true) {
		    CapiEvent event = capi.listen(7);
		    log("Event: " + event, false);
		    if(event.type == SimpleCapi.TIMEOUT) {
			//fax and dtmf are blocking events
			if(iAction == FAX ||iAction == DTMF) {
			    capi.hangup();
			    break;
			    //play, speak and record are non blocking events, wait for CAPI_EVENT_DONE
			} else {
			    continue;
			}
		    }
		    if(event.type == SimpleCapi.CAPI_EVENT_HANGUP) {
			break;
		    }
		    if(event.type == SimpleCapi.CAPI_EVENT_DONE) { // soundfile done playing - hang up
			capi.hangup();
			break;
		    }
		}
		
		log("Call to " + number2call + " ended", false);
	    } else {
		log("Couldn't connect to " + number2call + " within " + callTimeout + " sec!", false);
	    }
	} catch (IOException ex) {
	    log("IOException occured", true);
	    ex.printStackTrace();
	} catch (NoLineFreeException ex) {
	    log("NoLineFreeException occured", true);
	    ex.printStackTrace();
	} catch (CommandFailedException ex) {
	    log("CommandFailedException occured", true);
	    ex.printStackTrace();
	}
    }
    
    /**
     *plays an audio file to callee
     *@param file File to play to callee
     */
    private static void play(File file) throws IOException, CommandFailedException {
/*
	log("Transfering file " + file.getAbsolutePath() + file.getName() + "...", false);
	capi.put(file.getAbsolutePath(), file.getName());
	log("File transfered", false);
 */
	transferFile(file);
	log("playing file...", false);
	capi.play(file.getName());
	capi.del(file.getName());
    }
    
    /**
     *sends DTMFs to callee
     *@param beepcode DTMFs to send to callee
     */
    private static void beep(String beepcode) throws IOException, CommandFailedException {
	log("sending " + beepcode, false);
	capi.beep(beepcode);
    }
    
    /**
     *reads out some text to callee
     *capiserver has to be compiled with TTS<p>
     *@param text text to read out to callee
     */
    private static void readOut(String text) throws IOException, CommandFailedException {
	log("Speaking " + text, false);
	capi.speak(text);
    }
    
    /**
     *faxes a file to given number
     *@param file File to fax to callee,
     *allowed filetypes are sff, postscript and html
     */
    private static void fax(File file) throws IOException, CommandFailedException {
	transferFile(file);
	log("faxing file" + file.getName() + " to number " + number2call, false);
	capi.fax(number2call, file.getName());
	log("file faxed", false);
	capi.del(file.getName());
    }
    
    
    private static void transferFile(File file) {
	log("Transfering file " + file.getAbsolutePath() + file.getName() + "...", false);
	try {
	    capi.put(file.getAbsolutePath(), file.getName());
	} catch (CommandFailedException ex) {
	    log("Transfer of File " + file.getName() + " failed", true);
	    System.exit(ERRORCODE);
	} catch (IOException ex) {
	    log("IOException occured while transfering file " + file .getName(), true);
	    System.exit(ERRORCODE);
	}
	log("File transfered", false);
    }
    
    private static void log(String message, boolean is_error) {
	if(is_error) {
	    System.err.println("ERROR - " + message);
	} else if(debug) {
	    System.out.println("INFO - " + message);
	}
    }
    
    private static boolean validFile(File file, ArrayList allowed_extensions) {
	boolean returncode = false;
	//test file extension
	String filename = file.getName();
	String ext = (filename.lastIndexOf(".")==-1)?"":filename.substring(filename.lastIndexOf(".")+1,filename.length());
	
	if(file.isFile() && file.canRead() && allowed_extensions.contains(ext.toLowerCase())) {
	    returncode = true;
	    log(ext + " is a valid extension for action " + action, false);
	}
	return returncode;
    }
    
    private static void showUsage() {
	HelpFormatter formatter = new HelpFormatter();
	formatter.printHelp("caiviarclient", options);
    }
    
    private static void buildOptions() {
	options = new Options();
	Option o_help = new Option("h", "help", false, "print this message");
	Option o_file = new Option("f", "file", true, "played to callee");
	o_file.setArgName("file");
	Option o_debug = new Option("v", "verbose", false, "show extended status messages");
	Option o_action = new Option("a", "action", true, "specifies action to perform\n" +
		"must be one of call|dtmf|play|fax|speak");
	o_action.setArgName("action");
	Option o_number2call = new Option("n", "number", true, "number to call");
	o_number2call.setArgName("nr");
	Option o_serverport = new Option("p", "port", true, "port capiserver is listening on");
	o_serverport.setArgName("port");
	Option o_serveraddress = new Option("s", "server", true, "address of capiserver");
	o_serveraddress.setArgName("server");
	Option o_timeout = new Option("t", "timeout", true, "number of seconds to wait for an answer while calling");
	o_timeout.setArgName("s");
	
	options.addOption(o_help);
	options.addOption(o_debug);
	options.addOption(o_action);
	options.addOption(o_number2call);
	options.addOption(o_file);
	options.addOption(o_serverport);
	options.addOption(o_serveraddress);
	options.addOption(o_timeout);
    }
    
    private static void parseOptions(String[] args) {
	CommandLineParser parser = new GnuParser();
	
	try {
	    line = parser.parse(options, args);
	    
	    if(line.getOptions().length == 0 || line.hasOption("h")) {
		showUsage();
		System.exit(0);
	    }
	    
	    //verbose mode
	    if(line.hasOption("v")){
		debug = true;
	    }
	    
	    //getting action to perform
	    action = line.getOptionValue("a", "");
	    if(allowed_actions.contains(action.toLowerCase())) {
		log("performing action " + action, false);
	    } else {
		log("action \"" + action + "\" not valid", true);
		showUsage();
		System.exit(ERRORCODE);
	    }
	    
	    //test number format
	    number2call = line.getOptionValue("n", "");
	    log("number2call: " + number2call, false);
	    if(number2call.matches(RX_NUMBER2CALLFORMAT)){
		log("Calling " + number2call, false);
	    } else {
		log("number " + number2call + " has wrong format\n" +
			"try something like [+] 49 111 12341 4231", true);
		System.exit(ERRORCODE);
	    }
	    
	    //get caiviar server port
	    if(line.hasOption("p")) {
		if(line.getOptionValue("p").matches(RX_NUMBERFORMAT)) {
		    serverPort = Integer.parseInt(line.getOptionValue("p"));
		    if(serverPort > MAXPORT) {
			log("WARNING - Invalid server port: " + line.getOptionValue("p"), false);
			log("          using default value: " + DEFAULTPORT, false);
			serverPort = DEFAULTPORT;
		    }
		} else {
		    log("WARNING - Wrong format of server port: " + line.getOptionValue("p"), false);
		    log("          using default value: " + DEFAULTPORT, false);
		    serverPort = DEFAULTPORT;
		}
	    } else {
		serverPort = DEFAULTPORT;
		log("using default server port: " + DEFAULTPORT, false);
	    }
	    
	    //get caiviar server address
	    serverAddress = line.getOptionValue("s", String.valueOf(DEFAULTHOST));
	    try {
		serverInetAddress = InetAddress.getByName(serverAddress);
	    } catch (UnknownHostException ex) {
		log("ERROR - Invalid server address", true);
		System.exit(ERRORCODE);
	    }
	    
	    //get call timeout
	    if(line.hasOption("t")) {
		if(line.getOptionValue("t").matches(RX_NUMBERFORMAT)) {
		    callTimeout = Integer.parseInt(line.getOptionValue("t"));
		} else {
		    log("WARNING - Wrong format of call timeout: " + line.getOptionValue("t"), false);
		    log("          using default value: " + DEFAULTTIMEOUT + "s", false);
		    callTimeout = DEFAULTTIMEOUT;
		}
	    } else {
		log("using default timeout for call: " + DEFAULTTIMEOUT + "s", false);
	    }
	    
	    log("Connecting to " + serverPort + serverAddress + callTimeout, false);
	    
	    //Show first left over arg
	    if(line.getArgs().length > 0) {
		leftOverArg0 = line.getArgs()[0];
	    }
	    
	    //test file
	    filename = line.getOptionValue("f", "");
	    
	} catch (ParseException ex) {
	    ex.printStackTrace();
	    showUsage();
	    System.exit(ERRORCODE);
	}
    }
    
    private static void perform_action() {
	try {
	    switch(iAction) {
		case FAX:
		    log("faxing file " + faxFile.getName(), false);
		    fax(faxFile);
		    break;
		case CALL:
		    log("call", true);
		    //call();
		    break;
		case DTMF:
		    log("DTMF", true);
		    beep(leftOverArg0);
		    break;
		case READOUT:
		    log("Reading out text " + leftOverArg0 + " to callee", false);
		    readOut(leftOverArg0);
		    break;
		case PLAY:
		    log("Play file " + playFile.getName(), false);
		    play(playFile);
		    break;
		default:
		    log("unknown action", true);
	    }
	} catch(IOException e) {
	    e.printStackTrace();
	} catch(CommandFailedException e) {
	    e.printStackTrace();
	}
    }
    
    private static String readTextFile(File f) throws IOException {
	String ret = null;
	char[] buffer = new char[MAXREADOUTLENGTH];
	Reader r = new InputStreamReader(new FileInputStream(f));
	if(f.length() > MAXREADOUTLENGTH) {
	    log("File " + f + " to long to read out", true);
	    System.exit(ERRORCODE);
	} else {
	    r.read(buffer);
	    ret = new String(buffer);
	    log("Content to read out: " + ret, false);
	    r.close();
	}
	return ret;
    }
}