package com.marinilli.b2.c6.bank;

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

/**
 * Chapter 6 - The Bank Server
 *
 * @author Mauro Marinilli
 * @version 1.0
 */

public class BankServer implements Runnable {
  public final static char QUIT_COMMAND = 'q';
  public final static char GET_COMMAND = 'g';
  public final static char POST_COMMAND = 'p';
  public final static char CHECK_COMMAND = 'c';
  public final static String SERVER_DIR = "serverdir/";

  private DataOutputStream out;
  private DataInputStream in;
  private Properties downloadPolicy;
  private String header;
  private Socket clientSocket;
  private final static String PROPS_FILE_NAME = "downloadPolicy.properties";
  private final static String NEWEST_POLICY = "newest";
  private final static String NO_DOWNLOADS_POLICY = "none";
  private final static String DEFAULT_POLICY = NEWEST_POLICY;

  /**
   * Constructor
   */
  public BankServer(Socket s) {
    clientSocket = s;
    loadProperties();
  }

  /**
   * The Thread's run
   */
  public void run() {
    header = "BankServer["+Thread.currentThread().getName()+"] - ";
    System.out.println( header + "Connection Accepted.");

    try {
      out = new DataOutputStream(clientSocket.getOutputStream());
      in = new DataInputStream(clientSocket.getInputStream());
    } catch (IOException e) {
      System.out.println( header + "creating streams "+e);
      System.exit(-1);
    }
    char command = ' ';
    try {
      while ((command = in.readChar())!=-1) {
        // process incoming command
        if (command==QUIT_COMMAND) {
          System.out.println( header + "Command: QUIT.");
          System.out.println( header + "Client closed session.");
          break;
        }
        if (command==CHECK_COMMAND) {
          executeCheck();
        }
        if (command==GET_COMMAND) {
          //read the incoming string because it is the path to the requested file
          String path = in.readUTF();
          File f = new File(SERVER_DIR+path);
          int size = (int)f.length();
          out.writeInt(size);
          System.out.println( header + "Command: GET "+path+" size:"+size );
          if (f.exists()) {
            FileInputStream fis = new FileInputStream(f);
            sendToClient(new DataInputStream(fis), path);
          } else {
            // resource not found,
            // send back an empty msg
            sendToClient(new DataInputStream(new ByteArrayInputStream(new byte[0])), path+"(not found)");
          }
        }
        if (command==POST_COMMAND) {
          //read the incoming array because it is the posted data
          String data = in.readUTF();
          System.out.println( header + "Command: POST (data=\""+data+"\")");
        }
      }//-while
      System.out.println( header + "shutting down client connection");
      out.close();
      in.close();
      clientSocket.close();
      // to avoid troubles in a multi-threaded environment
//      saveProperties();
    } catch (IOException e) {
      System.out.println( header + "Main while: "+e);
      System.exit(-1);
    }
  }

  /**
   * implements the check Command, server-side
   */
  private void executeCheck() {
    // change name
    String tName = Thread.currentThread().getName();
    Thread.currentThread().setName("Deployment-"+tName);
    header = "BankServer["+Thread.currentThread().getName()+"] - ";
    try {
      String clientId = in.readUTF();
      String[] clientRecord = read(clientId);
      if (clientRecord==null) {
        write(clientId, DEFAULT_POLICY, "1.0", null, null);
        clientRecord = read(clientId);
      }
      String policy = clientRecord[0];
      String installedVersion = clientRecord[1];
      String replaceItems = clientRecord[2];
      String launchClass = clientRecord[3];
      if (launchClass==null)
        launchClass = "";// for protocol's sake

      System.out.println( header + "CHECK: for client: "+
                                 clientId+",\n\t policy: "+
                                 policy+",\n\t installedVersion: "+
                                 installedVersion+",\n\t replaceItems="+
                                 replaceItems+",\n\t launchClass="+launchClass);

      if (policy.equals(NEWEST_POLICY)) {
        // the client is required to download the new version
        String[] items = getItems(replaceItems);
        out.writeInt(items.length);
        File f;
        for (int i = 0; i < items.length; i++) {
          f = new File(SERVER_DIR + items[i]);
          out.writeUTF(items[i]);
          int size = (int)f.length();
          out.writeInt(size);
          FileInputStream fis = new FileInputStream(f);
          sendToClient(new DataInputStream(fis), items[i]);
        }
      } else if (policy.equals(NO_DOWNLOADS_POLICY)) {
        out.writeInt(0);
      }
      //last, notify to the client the new main class, if any
      out.writeUTF(launchClass);

    } catch (IOException e) {
      System.out.println( header + "executeCheck: "+e);
      System.exit(1);
    }
  }

  /**
   * It obtains tokens from a string
   */
  private String[] getItems(String s){
    ArrayList result = new ArrayList();
    StringTokenizer st = new StringTokenizer(s,";:");
    while (st.hasMoreTokens()) {
      String w = st.nextToken();
      result.add(w);
    }
    String[] a = new String[result.size()];
    result.toArray(a);
    return a;
  }

  /**
   * It sends data to the client
   */
  private void sendToClient(DataInputStream dis, String name){
    System.out.println( header + "sendToClient. " + name);
    byte[] buffer = new byte[1024];
    try {
      int read =0;
      while ((read = dis.read(buffer)) != -1) {
        out.write(buffer, 0, read);
      }
    } catch (IOException e) {
      System.out.println( header + "send data back to client: "+e);
    }
  }


  /**
   * Loads properties file
   */
  private void loadProperties(){
    try {
      downloadPolicy = new Properties();
      FileInputStream in = new FileInputStream(SERVER_DIR+PROPS_FILE_NAME);
      downloadPolicy.load(in);
      in.close();
    }
    catch (IOException ex) {
      System.out.println( header + "loadProperties(): "+ex);
    }
  }

  /**
   * writes some related property values
   */
  private void write(String client,
                     String policy,
                     String installedVersion,
                     String replaceItems,
                     String launchClass) {

    if (client==null)
      return;
    if (policy!=null)
      downloadPolicy.put(client+"-policy",policy);
    if (installedVersion!=null)
      downloadPolicy.put(client+"-installed",installedVersion);
    if (replaceItems!=null)
      downloadPolicy.getProperty(client+"-replace-items", replaceItems);
    if (launchClass!=null)
      downloadPolicy.getProperty(client+"-launch", launchClass);
  }

  /**
   * It reads some related property values
   */
  private String[] read(String client){
    if (client==null)
      return null;
    String[] ret = new String[4];
    String policy =
      downloadPolicy.getProperty(client+"-policy",DEFAULT_POLICY);
    String installedVersion =
      downloadPolicy.getProperty(client+"-installed", "1.0");
    String replaceItems =
      downloadPolicy.getProperty(client+"-replace-items", "");
    String launchClass =
      downloadPolicy.getProperty(client+"-launch", "BankClient");

    ret[0] = policy;
    ret[1] = installedVersion;
    ret[2] = replaceItems;
    ret[3] = launchClass;
    return ret;
  }

  /**
   * saves properties files
   */
  private void saveProperties(){
    try {
      FileOutputStream out = new FileOutputStream(SERVER_DIR+PROPS_FILE_NAME);
      downloadPolicy.store(out, "---Bank Server download Policies file---");
      out.close();
    }
    catch (IOException ex) {
      System.out.println( header + "saveProperties(): "+ex);
    }
    System.out.println( header + "save" );
  }

  /**
   * It launches the server
   */
  public static void main(String[] args) throws IOException {
    ServerSocket serverSocket = null;
    try {
      serverSocket = new ServerSocket(3333);
    } catch (IOException e) {
      System.out.println("BankServer - Could not listen on port: 3333: "+e);
      System.exit(-1);
    }
    System.out.println("BankServer - Waiting for Connections.");
    boolean listen = true;

    while(listen) {
      // connection started
      Thread t = new Thread(new BankServer(serverSocket.accept()));
      t.start();
    }
    serverSocket.close();
  }

}