/*
 * Author: Ruan Jue
 */

import java.io.*;
import java.net.*;
import java.util.*;

public class MaqDataClient {
	public static int VIEW_OP_CLOSE = 0;
	public static int VIEW_OP_INFO  = 1;
	public static int VIEW_OP_GOTO  = 2;
	public static int VIEW_OP_FETCH = 3;
	public static char[] nst_nt16_rev_table = {'X', 'A', 'C', 'M', 'G', 'R', 'S', 'V', 'T', 'W', 'Y', 'H', 'K', 'D', 'B', 'N'};

	Socket socket = null;
	int n_ref = 0;
	ArrayList ref_names   = new ArrayList();
	ArrayList ref_lengths = new ArrayList();
	ArrayList reads = new ArrayList(1024);
	StringBuffer ref_seqs = new StringBuffer(256);
	StringBuffer cns_seqs = new StringBuffer(256);
	int last_code = 0;
	int ref_id    = 0;
	long start    = 0;
	long end      = -1;

	class Read {
		byte seq[] = new byte[64];
		byte size;
		byte map_qual;
		byte info1;
		byte info2;
		byte c[] = new byte[2];
		byte flag;
		byte alt_qual;
		int  seqid;
		int  pos;
		int  dist;
		byte name[] = new byte[36];

		public int getRefId(){
			return seqid;
		}

		public long getPos(){
			return (long)(pos>>1);
		}

		public boolean isComplement(){
			return (pos&0x1) == 1;
		}

		public String getSeq(){
			char ACGT[] = {'A', 'C', 'G', 'T'};
			char acgt[] = {'a', 'c', 'g', 't'};
			int i;
			char[] str = new char[size];
			for(i=0;i<size;i++){
				if(seq[i] == 0){
					str[i] = 'N';
				} else if((seq[i]&0x3f) < 27){
					str[i] = acgt[(seq[i]>>6)&0x03];
				} else {
					str[i] = ACGT[seq[i]>>6];
				}
			}
			return new String(str);
		}

		public byte[] getSeqQuals(){
			byte[] qual = new byte[size];
			for(int i=0;i<size;i++){
				qual[i] = (byte)(seq[i]&0x3f);
			}
			return qual;
		}

		public int getMapQual(){
			return (int)map_qual;
		}

		public String getName(){
			int i = 0;
			while(i<36 && name[i] != 0){ i++;}
			return new String(name, 0, i);
		}

		public String toString(){
			return String.format("name[%s]\tseq[%s]\tqual[%d]\tpos[%d]\t%s", getName(), getSeq(), getMapQual(), getPos(), isComplement()? "Complement":"Forward");
		}

	}

	public static int converseInt(int v){
		int v1, v2, v3, v4;
		v1 = (v>>24)&0xFF;
		v2 = (v>>16)&0xFF;
		v3 = (v>>8)&0xFF;
		v4 = (v)&0xFF;
		return v1 | (v2<<8) | (v3<<16) | (v4<<24);
	}

	public static long converseLong(long v){
		long vs[] = new long[8];
		for(int i=0;i<8;i++){
			vs[i] = (v>>((7-i)*8))&0xFF;
		}
		v = 0;
		for(int i=0;i<8;i++){
			v |= vs[i] << (i * 8);
		}
		return v;
	}

	public static MaqDataClient connect(String host, int port) throws Exception {
		MaqDataClient client;
		DataInputStream input;
		client = new MaqDataClient();
		client.socket = new Socket(host, port);
		client.socket.getOutputStream().write(VIEW_OP_INFO);
		client.socket.getOutputStream().flush();
		input = new DataInputStream(client.socket.getInputStream());
		client.n_ref = converseInt(input.readInt());
		for(int i=0;i<client.n_ref;i++){
			client.ref_lengths.add(input.readLong());
			int len = converseInt(input.readInt());
			byte[] name = new byte[len];
			input.readFully(name);
			client.ref_names.add(new String(name));
		}
		return client;
	}

	public void getMaqData(int ref_id, long start, long end) throws Exception{
		DataOutputStream output;
		DataInputStream  input;
		output = new DataOutputStream(socket.getOutputStream());
		input  = new DataInputStream(socket.getInputStream());
		output.writeByte(VIEW_OP_FETCH);
		output.writeInt(converseInt(last_code));
		output.writeInt(converseInt(ref_id));
		output.writeLong(converseLong(start));
		output.writeLong(converseLong(end));
		output.flush();

		last_code = converseInt(input.readInt());
		ref_id    = converseInt(input.readInt());
		start     = converseLong(input.readLong());
		end       = converseLong(input.readLong());
		int size  = converseInt(input.readInt());
//		System.out.println(last_code+"\t"+ref_id+"\t"+start+"\t"+end+"\t"+size);
		reads.clear();
		for(int i=0;i<size;i++){
			Read read = new Read();
			input.readFully(read.seq);
			read.size     = input.readByte();
			read.map_qual = input.readByte();
			read.info1    = input.readByte();
			read.info2    = input.readByte();
			read.c[0]     = input.readByte();
			read.c[1]     = input.readByte();
			read.flag     = input.readByte();
			read.alt_qual = input.readByte();
			read.seqid    = converseInt(input.readInt());
			read.pos      = converseInt(input.readInt());
			read.dist     = converseInt(input.readInt());
			input.readFully(read.name);
			input.skipBytes(8);
			reads.add(read);
		}
		size  = converseInt(input.readInt());
		ref_seqs.delete(0, ref_seqs.length());
		cns_seqs.delete(0, cns_seqs.length());
		for(int i=0;i<size;i++){
			input.skipBytes(7);
			byte b = input.readByte();
			cns_seqs.append(nst_nt16_rev_table[b&0xF]);
			ref_seqs.append(nst_nt16_rev_table[b>>4]);
		}
	}

	public void close() throws Exception{
		socket.getOutputStream().write(VIEW_OP_CLOSE);
		socket.close();
	}

	public static void main(String[] argv) throws Exception {
		if(argv.length < 5){
			System.out.println("Params: <host> <port> <ref_id> <start> <end>");
			return;
		}
		MaqDataClient client = MaqDataClient.connect(argv[0], Integer.parseInt(argv[1]));
		client.getMaqData(Integer.parseInt(argv[2]), Long.parseLong(argv[3]), Long.parseLong(argv[4]));
		System.out.println(client.ref_seqs);
		System.out.println(client.cns_seqs);
		for(int i=0;i<client.reads.size();i++){
			System.out.println(client.reads.get(i));
		}
		client.close();
	}

}
