package com.algobase.share.network;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

import java.lang.StringBuilder;

import java.util.Random;
import java.security.MessageDigest;

import java.net.Socket;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;

import android.util.Base64;


public class WebSocket {

  static byte OPCODE_CONTINUATION = (byte)0x00;
  static byte OPCODE_TEXT   = (byte)0x01;
  static byte OPCODE_BINARY = (byte)0x02;
  static byte OPCODE_CLOSE  = (byte)0x08;
  static byte OPCODE_PING   = (byte)0x09;
  static byte OPCODE_PONG   = (byte)0x0a;
  static byte OPCODE_ERROR  = (byte)0xff;

  public void writeLog(String s) {}
  public void showToast(String s) {}
  public void acknowledge(String title, String s) {}


    //Socket sock;
    SSL_Socket sock;

    String host;
    int port;

    byte[] buffer = new byte[1024];
    int buf_bytes = 0;
    int buf_p = 1024;
	

  static String sha1Base64(String str)
  { byte[] bytes = null;
  
    try {
      MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
      messageDigest.update(str.getBytes("UTF-8"));
      bytes = messageDigest.digest();
    }
    catch (Exception ex) { }
  
    if (bytes != null)
      return Base64.encodeToString(bytes,Base64.DEFAULT);
    else
      return "";
  }


	
   
  public WebSocket()
  {  sock =  new SSL_Socket();
     sock.setConnectTimeout(5000);
     sock.setReceiveTimeout(0);
   }

  public void setConnectTimeout(int msec) { 
    sock.setConnectTimeout(msec);
  }

  public void setReceiveTimeout(int msec) { 
    sock.setReceiveTimeout(msec);
  }

  public boolean connected() { 
    return sock.connected();
  }
  
  public boolean connect(String host, int port) { 
    this.host = host;
    this.port = port;
    return sock.connect(host,port);
  }


  public int NEXT_BYTE() 
  { 
/*
   if (buf_p >= buf_bytes)
    { buf_p = 0;
      buf_bytes = sock.receiveBytes(buffer);
      if (buf_bytes <= 0) return -1;
    }
    return buffer[buf_p++];
*/
    return sock.receiveByte();
  }

  
  public String getError() { return sock.getError(); }

  public boolean disconnect() { 
     writeLog("WEBSOCKET: DISCONNECT");
     return sock.disconnect(); 
  }

  public String handshake(String path)
  {
    //showToast("handshake: " + path);

    // random key (16 bytes)

    Random rand = new Random();
    byte[] random_bytes = new byte[16];
    for(int i=0; i<16; i++) random_bytes[i] = (byte)rand.nextInt(256);

    String key = Base64.encodeToString(random_bytes,Base64.DEFAULT);


    // send request header
/*
     sock.sendString("GET /" + path + " HTTP/1.1\r\n");
     sock.sendString("Host: " + host + ":" + port + "\r\n");
     sock.sendString("Upgrade: websocket\r\n");
     sock.sendString("Connection: Upgrade\r\n");
     sock.sendString("Sec-WebSocket-Key: " + key + "\r\n");
     sock.sendString("Origin: " + host + "\r\n");
   //sock.sendString("Sec-WebSocket-Protocol: chat, superchat\r\n");
     sock.sendString("Sec-WebSocket-Version: 13\r\n");
     sock.sendString("\r\n");
*/
     String txt = "";
     txt += ("GET /" + path + " HTTP/1.1\r\n");
     txt += ("Host: " + host + ":" + port + "\r\n");
     txt += ("Upgrade: websocket\r\n");
     txt += ("Connection: Upgrade\r\n");
     txt += ("Sec-WebSocket-Key: " + key + "\r\n");
     txt += ("Origin: " + host + "\r\n");
   //txt += ("Sec-WebSocket-Protocol: chat, superchat\r\n");
     txt += ("Sec-WebSocket-Version: 13\r\n");
     txt += ("\r\n");

     //acknowledge("SENDING", txt);

     sock.sendString(txt);

    // receive header
    txt = "";
    while (txt.length() < 4 || !txt.endsWith("\r\n\r\n"))
    { int b = NEXT_BYTE();
      if (b < 0) break;
      txt += (char)b;
     }

    //acknowledge("HEADER", txt);

    return txt;
  }

  boolean accept()
  { 
    buf_bytes = 0;
    buf_p = 1024;

    // receive header
    String txt = "";
    while (txt.length() < 4 || !txt.endsWith("\r\n\r\n"))
    { int b = NEXT_BYTE();
      if (b < 0) break;
      txt += (char)b;
     }

    String ws_key = "";
    String str = "Sec-WebSocket-Key:";
    int p = txt.indexOf(str);
    if (p != -1) {
      p += str.length();
      int q = txt.indexOf("\r\n");
      if (q != -1) ws_key = txt.substring(p,q).trim();
    }

    String magic = ws_key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";

    String b64 = sha1Base64(magic);

    sock.sendString("HTTP/1.1 101 Switching Protocols\r\n");
    sock.sendString("Upgrade: websocket\r\n");
    sock.sendString("Connection: Upgrade\r\n");
    sock.sendString("Sec-WebSocket-Accept: " + b64 + "\r\n");
    sock.sendString("\r\n");

    return true;
  }


  public boolean send_frame(byte opcode, byte[] buf, int off)
  { 
    // send data buf[off .. buf.lengh-1]

    if (!sock.connected()) return false;

    int len = buf.length - off;
    int frame_sz = len + 2;

    if (len >= 0xffff) frame_sz += 8;
    else if (len >= 126) frame_sz += 2;

    if (!host.equals("")) 
    { // client: 4 masking bytes
      frame_sz += 4;
    }

    // construct frame

    byte[] frame = new byte[frame_sz];
    int p = 0;

    frame[p++] = (byte)(0x80 | opcode);  // 0x80 (fin) + opcode

    if (len < 126)
      frame[p++] = (byte)len;
    else
      if (len < 0xffff)
      { frame[p++] = 126; 
        frame[p++] = (byte)((len >> 8) & 0xff);
        frame[p++] = (byte)((len >> 0) & 0xff);
       }
     else
     { frame[p++] = 127;
       frame[p++] = (byte)((len >> 56) & 0xff);
       frame[p++] = (byte)((len >> 48) & 0xff);
       frame[p++] = (byte)((len >> 40) & 0xff);
       frame[p++] = (byte)((len >> 32) & 0xff);
       frame[p++] = (byte)((len >> 24) & 0xff);
       frame[p++] = (byte)((len >> 16) & 0xff);
       frame[p++] = (byte)((len >>  8) & 0xff);
       frame[p++] = (byte)((len >>  0) & 0xff);
     }
  
     byte[] mask_key = new byte[4]; // zero

     if (!host.equals("")) 
     { // ws client: set mask bit
       frame[1] |= 0x80;
       //generate random mask bytes
      Random rand = new Random();
      for(int i=0; i<4; i++) 
      { byte r = (byte)rand.nextInt(256);
        mask_key[i] = r;
        frame[p++] = r;
       }
     }

     for(int i=0; i<len; i++) frame[p++] = (byte)(buf[i+off] ^ mask_key[i%4]);
  
     sock.sendBytes(frame);
   
     return true;
  }
  

  byte[] read_frame()
  { 
    // code byte will be stored in buf[0] and data in buf[1 .. ]
  
    if (!sock.connected()) return null;
  
    // read first code byte 
    int code = NEXT_BYTE();
    if (code < 0) return null;
  
    //fin    = (code & 0x80) != 0;
    //opcode = code & 0x0f;
  
    // read second byte
    int x = NEXT_BYTE();
    if (x < 0) return null;
  
    boolean masked = (x & 0x80) != 0;
    int len = x & 0x7f;
  
    if (len > 125)
    { int k = 0;
      if (len == 126) k = 2;
      if (len == 127) k = 8;
      len = 0;
      for(int i=0; i<k; i++) len = 256*len + NEXT_BYTE();
    }
  
  
    // unmasking
  
    int[] masking_key = new int[4]; // zeros
  
    if (masked) {
      for(int i=0; i<4; i++) masking_key[i] = NEXT_BYTE();
    }
  
    byte[] buf = new byte[len+1];
  
    buf[0] = (byte)code;
  
    for(int i=1; i<=len; i++) {
       buf[i] = (byte)(NEXT_BYTE() ^ masking_key[i%4]);
    }
  
    return buf;
  }
  
  
  public String receive_text()
  { 
    String result = "";
    boolean fin = false;
    int opcode = -1;
  
    writeLog("WEBSOCKET: receive_text");
  
    while (!fin || (opcode != OPCODE_TEXT && opcode != OPCODE_CONTINUATION)) 
    { 
      byte[] buf = read_frame();
      // buf[0] encodes(fin & opcode)
      // buf[1 .. len-1] data
  
      if (buf == null) {
        writeLog("WEBSOCKET: buf == null");
        break;
      }

      writeLog("buf.length = " + buf.length);
  
      fin = (buf[0] & 0x80) != 0;
      opcode = buf[0] & 0x0f;
  
      if (opcode == OPCODE_ERROR)
      { writeLog("OPCODE_ERROR");
        disconnect();
        result = "";
        break;
       }
  
      if (opcode == OPCODE_PING)
      { writeLog("PING --> PONG");
        send_frame(OPCODE_PONG,buf,1);
        return "PING: " + new String(buf,1,buf.length-1);
       }
    
      if (opcode == OPCODE_PONG) {
        writeLog("OPCODE_PONG");
        return "PONG: " + new String(buf,1,buf.length-1);
      }
  
      if (opcode == OPCODE_CLOSE)
      { writeLog("OPCODE_CLOSE");
  
        int code = -1;
        String reason = "none";
        if (buf.length > 1) code = 256*buf[0] + buf[1];
        if (buf.length > 2) reason = new String(buf,2,buf.length-2);
  
        // send back close frame and disconnect
        send_frame(OPCODE_CLOSE,buf,1);
        disconnect();
        result = "";
        break;
       }
  
      if (opcode == OPCODE_TEXT || opcode == OPCODE_CONTINUATION)
      { if (result != "" && opcode != OPCODE_CONTINUATION) {
          //"EXPECTED: " << "OPCODE_CONTINUATION" << endl;
        }
  
       result += new String(buf,1,buf.length-1);
       continue;
      }
  
    }
  
    return result;
  }
  
  
  public void send_text(String text) { 
    send_frame(OPCODE_TEXT,text.getBytes(),0);
  }
  
  public void send_data(byte[] data) { 
    send_frame(OPCODE_BINARY,data,0);
  }
  
  public void ping(String text) { 
    send_frame(OPCODE_PING,text.getBytes(),0);
  }
  
  public void pong(String text) { 
    send_frame(OPCODE_PONG,text.getBytes(),0);
  }
  
}

