/*
XOWA: the XOWA Offline Wiki Application
Copyright (C) 2012 gnosygnu@gmail.com

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

This program 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 Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
/*
XOWA TCP console: A TCP console example for XOWA
Copyright (C) 2013 gnosygnu@gmail.com

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

This program 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 Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package gplx.xowa;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Xowa_tcp_console {
	public static void main(String[] args) {
		Xowa_tcp_console console = new Xowa_tcp_console();
		console.Run(args);
	}
	private int server_send_port;
	private int server_recv_port;
	private String wiki_domain;
	private int max_length;
	private Xowa_tcp_sender sender;
	public void Run(String[] args) {
        Print_message_line("XOWA TCP client v0.0.0.0");
        
        // parse args
        if (!Parse_args(args)) {
			Print_message_line("XOWA console requires 4 args: server_send_port, server_recv_port, wiki_domain, max_length.");
			Print_message_line("For example, use '55000 55001 simple.wikipedia.org 1000'");
			return;
        }

        // start sender
        sender = new Xowa_tcp_sender(server_send_port);
        
        // start console
        BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
        Print_message_line("Enter page name. For example 'Earth'. Press Ctrl+C to exit. Enter '|server_stop' to stop server or '|exit' to exit.");
        Print_message("> ");
        try {
	        while (true) {
	        	String page = Read_string(input);
	        	if 		(page == null)
	        		break;
	        	else if ("|exit".equals(page))
	        		break;
	        	else if ("|server_stop".equals(page)) {
	        		Send_message_server_stop();
	        	}
	        	else
	        		Send_message_fetch_page(page);
	        }
        }
	    catch (Exception e) {
	    	Print_error(e);
	    }
	}
	private void Send_message_fetch_page(String page) {
		Print_message_line("Sending request for " + page);
		// String xowa_msg = "0|0000000128|0000000257|xowa.cmd.exec|id_0|sender_0|recipient_0|2013-07-18 01:23:45.678|app.shell.fetch_page('simple.wikipedia.org/wiki/Earth', 'html');";		
		String command = String.format("app.shell.fetch_page('%s/wiki/%s', 'wiki');", wiki_domain, page);
		Send_message(command);
	}
	private void Send_message_server_stop() {	
		Print_message_line("Sending request to stop server");
		Send_message("app.server.stop;");
	}
	private void Send_message(String command) {
		String id = "id_is_for_client_usage";
		String time = "time_is_for_client_usage";
		String body = String.format("xowa.cmd.exec|%s|xowa_tcp_console|xowa_server|%s|%s", id, time, command);
		int body_len = body.length();
		int cksum = (body_len * 2) + 1;
		String msg = String.format("0|%s|%s|%s", String.format("%010d", body_len), String.format("%010d", cksum), body);

		Xowa_tcp_receiver receiver = new Xowa_tcp_receiver(server_recv_port, max_length);
        new Thread(receiver).start();
    	sender.Send_command(msg);
	}
	private boolean Parse_args(String[] args) {
		if (args.length != 4) {
			Print_message_line("4 arguments must be supplied: " + args.length);
			return false;
		}
		server_send_port = Parse_int(args[0]); 	if (server_send_port == -1) return false;
		server_recv_port = Parse_int(args[1]); 	if (server_recv_port == -1) return false;
		wiki_domain = args[2];
		max_length = Parse_int(args[3]); 		if (max_length == -1) return false;
		return true;
	}
	private static int Parse_int(String raw) {
		try {return Integer.parseInt(raw);}
		catch (Exception e) {
			Print_message_line("argument must be numeric: " + raw);
			return -1;
		}
	}
	private static String Read_string(BufferedReader input) {
        try {return input.readLine();}
        catch (IOException e) {return null;}		
	}
	public static void Print_message_line(String msg) {
        System.out.println(msg);		
	}
	public static void Print_message(String msg) {
        System.out.print(msg);		
	}
	public static void Print_error(Exception e) {
        System.err.println(e.getMessage());		
	}
	public static void Sleep(long millis) {
		try {Thread.sleep(millis);} 
		catch (InterruptedException e) {Print_error(e);}
	}
}
class Xowa_tcp_sender {
	private int port;
	private Socket socket;
	private OutputStream output_stream;
	public Xowa_tcp_sender(int port) {this.port = port;}
	public boolean Open_socket() {
		try {			
			socket = new Socket("localhost", port);
			// socket.setSoTimeout(10000);
			output_stream = socket.getOutputStream();
			return true;
		}
		catch (Exception e) {
			Xowa_tcp_console.Print_error(e);
			return false;
		}
	}
	public void Send_command(String msg) {
		try {
			while (!Open_socket()) {
				Xowa_tcp_console.Sleep(100);
			}
			byte[] buffer = msg.getBytes();
			output_stream.write(buffer, 0, buffer.length);
			Close_socket();
		} 
		catch (Exception e) {
			Xowa_tcp_console.Print_error(e);
		}
	}
	public void Close_socket() {
		try {
			output_stream.close();
			socket.close();
		} 
		catch (Exception e) {
			Xowa_tcp_console.Print_error(e);
		}		
	}
}
class Xowa_tcp_receiver implements Runnable {
	private int port;
	private int max_length;
	private ServerSocket server_socket;
	private Socket client_socket;
	private InputStream input_stream;
	public Xowa_tcp_receiver(int port, int max_length) {
		this.port = port;
		this.max_length = max_length;
	}
	public void run() {
		try {
			// initialization
			server_socket = new ServerSocket(port);
			client_socket = server_socket.accept();
			// client_socket.setSoTimeout(10000);
			byte[] buffer = new byte[65536];
			input_stream = client_socket.getInputStream();
			
			// read incoming messages
			int read = 0;
			while (true) {
				String msg = "";
				// read header
				int body_len_max = 0, body_len_cur = 0;
				read = input_stream.read(buffer, 0, 24);
				if (read == -1) break;
				String body_len_max_str = new String(buffer, 2, 10);
				body_len_max = Integer.parseInt(body_len_max_str);
				buffer = new byte[body_len_max];
				
				// read rest of body
				while (body_len_cur < body_len_max) {
					read = input_stream.read(buffer);
					if (read == -1) break;
					body_len_cur += read;
					msg += new String(buffer, 0, read);
				}
				
				int msg_length = msg.length();
				if (msg_length > max_length) msg_length = max_length;
				Xowa_tcp_console.Print_message_line(msg.substring(0, msg_length));
				Xowa_tcp_console.Print_message("\n\n> ");
			}
			this.Close_socket();
		}
		catch (Exception e) {
			Xowa_tcp_console.Print_error(e);
		}		
	}
	public void Close_socket() {
		try {
			input_stream.close();
			client_socket.close();
			server_socket.close();		
		}
		catch (Exception e) {
//			Xowa_tcp_console.Print_error(e);	// ignore, else error will print in console 
		}		
	}
}
