//NetBull.java -- by tsaiwn@csie.nctu.edu.tw
//javac NetBull.java CowBull.java
//java NetBull  [port_number]    
//then you can use "telnet hostName 3388" to connect this program
//where "hostName" is the IP of the machine running this program
//Note that there are 3 classes in 2 Java files.
import java.io.*;
import java.net.*;
import java.util.*;  // Date is in this package
import java.lang.*;  // NOT necessary
class NetBull  {  // should be same as the File name, 建議該用大寫開頭 NetBull
    ServerSocket ssk;   //  用來等待連線
    Socket ccc;    // 有client連線進來可生出 Socket 小洞
    static final int DEFAULT_PORT = 3388;  // final 表示後面不可改它 
    static final int TIME_OUT = 300;  // 300 seconds
    int portNumber;    // Listening TCP port
    PrintStream cout = System.out;    // 取個與 C++ 同名的物件變數
    PrintStream cerr = System.err;
  static int getPortNumber(String str) {  // get port number on command line
     int ans=0;                   //For example:  java NetBull 5678
     try {
        ans = Integer.parseInt(str);
     }catch(Exception e) { ans = 0; }
     return ans;
  } // getPortNumber if any, for example:   java NetBull 5566
  ///
  public static void main(String[ ]  xxx) throws Exception {
                                        // ^^^ 宣稱若出問題會報告馬政府 !
        int pp=0;  // 若 command line 有 port number 就用它 
        if(xxx.length > 0) pp =getPortNumber(xxx[0]);  // array 的 length
        if(pp==0) pp = DEFAULT_PORT;  // 有打就用 user 打的, 否則用原來的
        checkFiles( );  // 若有錯就不會回來 :-)
        // OK now
        new NetBull(pp);  // 要求生出 NetBull 物件並立刻執行 NetBull( ) 建構子
  }// main
  ///
  NetBull(int port) {  // 這就是 class NetBull 的 建構子 constructor 
      portNumber = port;
      cout.println("Server started ... at port "+ portNumber);
      try {
         ssk = new ServerSocket( portNumber );
         ssk.setSoTimeout(TIME_OUT * 1000);   //  time out = 300 sec ?
         while(38 == 38) { // true
            try {
               ccc = ssk.accept( );  // 等待連線後取得連線暫稱 ccc (Socket)
            }catch(Exception e) {
               cerr.println(" .. " + TIME_OUT/60.0  // to minutes
                        + " minutes past "+new Date( ));
               continue; // to Listen again
            }
            Brother b = new Brother(ccc); // 請一個小弟, 並把連線ccc交給小弟 
            b.start( );  // 叫小弟開始處理該連線, 執行它的 run( )  
         } // while
      } catch(Exception e) {
      cerr.println("Cannot startup ChatServer at port "+ portNumber+"!");
      cerr.println("Maybe port "+portNumber+" has been used?\nByebye.");
      }//try
      cerr.println("...Server stop.");
  } // NetBull( )
  static void checkFiles( ) { // 確定有 Brother.class 和 CowBull.class
        try{
            Class cc = Class.forName("CowBull"); // try to find CowBull.class
        }catch(Exception e) {
            System.err.println(" ? can NOT find CowBull.class!");
            System.exit(38);
        } // try
        try{
            Class cc = Class.forName("Brother"); // try to find Brother.class
        }catch(Exception e) {
            System.err.println(" can NOT find Brother.class!");
            System.exit(49);
        }
  }// checkFiles
} // class NetBull
/// /// //
class Brother extends Thread { // 表示這 class Brother 是一個 Thread 執行緒 
    Socket mySock;  // 用來記住"老大"傳來的資料, 方便class內其他 function 用 
    Date now;
    String name="???";  // remember user name
    private String remoteAddr;   // 連著我的對方 IP address
    private BufferedReader in;
    private PrintStream out;
    private PrintStream cout = System.out;   // cout 只是簡寫而已
    Brother(Socket x) { 
        mySock = x; 
        now = new Date( );
        try {
            remoteAddr = mySock.getInetAddress().getHostAddress();
              // 然後取得InputStream並包成 BufferedReader 方便 readLine()
            in = new BufferedReader(
                    new InputStreamReader(mySock.getInputStream()) );
              // 再取得 OutputStream 並包成 PrintStream
            out = new PrintStream(
                    (mySock.getOutputStream()), true );
            cout.println("Friend from " + remoteAddr + " at " + now);
        } catch(Exception e) {
          // say something
        }
    } // Brother constructor
    public void run( ) {   // 規定 Thread 內一定要這樣寫才有用
        name = "???";     // 接著, 可以 與 連線者對話
        try { 
            out.print("Welcome to the BullCow Land.\n\r"); out.flush( );
            while(name.length( ) < 7) {  // 不可能短於 7 :-)
               out.print("Give me your 學號與姓名: ");
               name = in.readLine( );
               name = edit(name);   // 去除 Backspace 等等 ..
               if(name.startsWith("   ") || name.length( )< 6 ) { 
                   name = "???";
                   out.print(" ! 不可亂打, ");
               }//if
            }//while
            //cout.println(" length=" + name.length( ) +" Name: "+name);
            out.println("歡迎 " + name + " 開始玩, 公牛是位置對數字也對!\n\r");
            cout.println("  " + name + " from " + remoteAddr + 
                  " play on " + new Date( ) +"..");
            log(1, name + " @" + remoteAddr+" " + new Date( ) ); // 1=start
            CowBull cb = new CowBull(in, out, this);  // this 指到我 !
              /// cb.play( );  // auto play in constructor 
            out.println("\n\rBye bye " + name + " at " + new Date( )); 
            log(0, name + " leaving " + new Date( ) ); // 0= leaving
            try { mySock.close( ); }catch(Exception e) {  } 
        }catch(Exception e) {  }
        cout.println("  " + name + " leaving at " +
                     new Date( ) +"..."+ remoteAddr);
    } // run( )
 String edit(String str) {    // 去掉 str 中的 Backspace 以及前後空白 
    String t = str.trim( );
    char u[ ] = t.toCharArray( );   // 轉為 char array [ ]
    int p = 0;  
    int last = u.length;
    for(int i=0; i< last; ++i) { // 127 為 DEL key 一般不處理 
       //System.out.print(" :" + (int)u[i]);
       if((int)u[i] == 127){  continue; }  // DEL key
       if((int)u[i] == 8) { if(p>=0) --p; if(p>=0) --p;} //Backspace
       else u[p] = u[i];   // copy 到左邊尾巴 
       ++p; 
    }
    while(p < last) u[p++] = ' ';  // 後面都清掉 
    t = new String(u);  // 轉回 String
    return t.trim( );   // 再去掉頭尾空白 
 }//edit
    void log(int k, String s) {  // Log into log file
        FileWriter f;
        PrintWriter logFile;
        try {
           f = new FileWriter(logFileName, true);  // true to Append
           logFile = new PrintWriter(f);
        }catch(Exception e) { return; }  // No log if fail
        if(k == 0) logFile.append(" ..");  // leaving 
        logFile.append(s +"\r\n");
        logFile.close( );
        return;
    } // log
    String logFileName = "bullLog.txt";   // FileName of the Log file 
} // class Brother
