package senseiTests.concurrency;

import sensei.util.Configuration;
import sensei.util.Error;
import sensei.util.ErrorHandler;
import sensei.util.logging.Logger;
import sensei.util.ParameterException;
import sensei.util.Parameters;

import sensei.domains.DomainGroupHandlerImpl;
import sensei.gmns.GroupMembershipBasicServiceImpl;
import sensei.gmns.GroupMembershipNamingServiceImpl;
import sensei.gmns.GroupMembershipNamingServiceFactory;
import sensei.domains.DynamicSubgroupInfoAsStringImpl;
import sensei.domains.GroupMonitorImpl;

import sensei.middleware.gmns.GroupHandlerFactory;
import sensei.middleware.gms.GroupHandler;
import sensei.middleware.gmns.GroupHandlerFactoryImpl;
import sensei.middleware.gmns.GroupMembershipNamingService;
import sensei.middleware.domains.BehaviourOnViewChanges;
import sensei.middleware.domains.DomainGroupUserBaseImpl;
import sensei.middleware.domains.DomainExpulsionReason;
import sensei.middleware.domains.DomainGroupHandler;
import sensei.middleware.domains.GroupMonitor;
import sensei.middleware.domains.MonitorException;
import sensei.middleware.domains.SubgroupsHandlerException;
import sensei.middleware.domains.TransactionException;
import sensei.middleware.util.ObjectsHandling;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;

public class TestMonitor extends DomainGroupUserBaseImpl implements Runnable, ErrorHandler
{
  DynamicSubgroupInfoAsStringImpl myInfo;
  Map monitors;
  DomainGroupHandler domainGroup;
  String lastInputLine;
  GroupMonitor monitor;
  int monitorNumber;
  ChatMember chat;
  Printer printer;

  final static int UNKNOWN = 0;
  final static int LOCK = 1;
  final static int UNLOCK = 4;
  final static int START_TRANSACTION = 11;
  final static int END_TRANSACTION = 14;
  final static int CREATE_MONITOR = 21;
  final static int REMOVE_MONITOR = 25;
  final static int MENU = 52;
  final static int QUIT = 100;

  public void run()
  {
    try
    {
      BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
      int input=UNKNOWN;
      printHelp();
      while(input!=QUIT)
      {
        input=readInput(reader);
        switch(input)
        {
          case CREATE_MONITOR:
            createMonitor();
            break;
          case END_TRANSACTION:
            endTransaction();
            break;
          case LOCK:
            lockMonitor(monitor, monitorNumber);
            break;
          case MENU:
            printHelp();
            break;
          case QUIT:
            printer.print("Leaving the group....");
            domainGroup.leaveGroup();
            break;
          case REMOVE_MONITOR:
            removeMonitor(monitorNumber);
            break;
          case START_TRANSACTION:
            startTransaction(monitor, monitorNumber);
            break;
          case UNLOCK:
            unlockMonitor(monitor, monitorNumber);
            break;
          case UNKNOWN:
          default:
            chat.castString(lastInputLine);
        }
      }
    }
    catch(Exception ex)
    {
      ex.printStackTrace();
      System.exit(0);
    }
  }

  void createMonitor() throws Exception
  {
    printer.print("Creating monitor.....");
    GroupMonitorImpl.register(myInfo, domainGroup);
  }

  void removeMonitor(int number) throws Exception
  {
    printer.print("Removing monitor "+number+"....");
    try
    {
      domainGroup.removeSubgroup(number, myInfo);
    }
    catch(SubgroupsHandlerException sex)
    {
      printer.print("Error removing the monitor. Static subgroups can not be removed!");
    }
  }

  void lockMonitor(GroupMonitor who, int number) throws Exception
  {
    printer.print("Locking monitor "+number+"....");
    who.lock();
    printer.print("Monitor " + number + " locked!!!!");
  }

  void unlockMonitor(GroupMonitor who, int number) throws Exception
  {
    printer.print("Unlocking monitor "+number+"....");
    try
    {
      who.unlock();
      printer.print("Monitor "+number+" unlocked!!!!");
    }
    catch(MonitorException ex)
    {
      ex.printStackTrace();
      printer.print("This monitor "+number+" has not the lock!");
    }
  }

  void startTransaction(GroupMonitor who, int number) throws Exception
  {
    printer.print("Starting transaction "+number+"....");
    domainGroup.startTransaction(who);
    printer.print("Transaction " + number + " started!!!!");
  }

  void endTransaction() throws Exception
  {
    printer.print("Ending transaction ....");
    try
    {
      domainGroup.endTransaction();
      printer.print("Transaction ended!!!!");
    }
    catch(MonitorException ex)
    {
      printer.print("Incorrect transaction on the given monitor");
    }
    catch(TransactionException ex)
    {
      printer.print("System is not on a transaction");
    }
  }

  int readInput(BufferedReader reader) throws IOException
  {
    do
    {
      printer.onInputRequest();
      lastInputLine = reader.readLine();
      printer.inputGiven();
      if (lastInputLine!=null)
        return processLine(lastInputLine.toUpperCase());
    }
    while(lastInputLine!=null);
    return QUIT;
  }

  int processLine(String line)
  {
    if (line.equals("Q"))
      return QUIT;
    else if (line.equals("?"))
      return MENU;
    else if (line.equals("E"))
      return END_TRANSACTION;
    else if (line.equals("C"))
      return CREATE_MONITOR;
    else if (line.startsWith("L"))
      return isValidMonitor(line.substring(1), LOCK);
    else if (line.startsWith("U"))
      return isValidMonitor(line.substring(1), UNLOCK);
    else if (line.startsWith("S"))
      return isValidMonitor(line.substring(1), START_TRANSACTION);
    else if (line.startsWith("R"))
      return isValidMonitor(line.substring(1), REMOVE_MONITOR);
    return UNKNOWN;
  }

  int isValidMonitor(String line, int ret)
  {
    try
    {
      Integer number = Integer.valueOf(line);
      monitor = (GroupMonitor) monitors.get(number);
      if (monitor!=null)
      {
        monitorNumber=number.intValue();
        return ret;
      }
    }
    catch(NumberFormatException nex){}
    return UNKNOWN;
  }

  void printHelp()
  {
    printer.print ("----------------------- HELP --------------------------------\n"
                      + "~~~~~~ (Lx): lock monitor ---------  (Ux): unlock monitor\n"
                      + "~~~~~~ (Sx): start Transaction ----  (E): end Transaction\n"
                      + "~~~~~~ (C): create monitor --------  (Rx): remove monitor\n"
                      + "~~~~~~ (?): shows this help --------  (Q): quit\n"
                      + "  The initial monitors are x=1,2,3\n"
                      + "-------------------------------------------------------------");
  }

  public void domainAccepted(int id)
  {
    printer.print("Member accepted; id="+id);
    myInfo = new DynamicSubgroupInfoAsStringImpl(String.valueOf(id));
  }

  public void offendingSubgroup(int who, String reason)
  {
    printer.print("Detected a problem on subgroup "+who+" ("+reason+"). To be shortly expulsed...");
  }

  public void stateObtained(boolean assumed)
  {
    printer.print("State obtained, monitor's functionality becomes available");
    new Thread(this).start();
  }

  public void domainExpulsed(DomainExpulsionReason reason)
  {
    printer.print("Member expulsed, reason="+reason.value());
    System.exit(0);
  }

  public TestMonitor(Factory factory, Parameters parameters)  throws ParameterException, Exception
  {
    printer = new Printer();
    Error.setErrorHandler(this);

    String refFile = parameters.hasParameter(REFFILE)? parameters.getParameter(REFFILE) : null;
    String connectFile = parameters.hasParameter(CONNECT)? parameters.getParameter(CONNECT) : null;
    String groupName = parameters.hasParameter(GROUP)? parameters.getParameter(GROUP) : null;
    String name;
    if (parameters.hasParameter(NAME))
    {
      name = parameters.getParameter(NAME);
      Logger.setLogName(name);
    }
    else
      name = "unknown";

    domainGroup = new DomainGroupHandlerImpl(). theDomainGroupHandler();
    domainGroup.setDomainGroupUser(theDomainGroupUser());
    domainGroup.setBehaviourMode(BehaviourOnViewChanges.MembersOnTransferExcludedFromGroup);

    monitors = Collections.synchronizedMap(new HashMap());

    domainGroup.setDynamicSubgroupsUser(new DynamicCreator(domainGroup, monitors, printer).theDynamicSubgroupsUser());

    //A GroupHandler is required, obtaining first the appropiated factory
    GroupHandler toStore=null;;
    GroupHandlerFactory ghFactory=new GroupHandlerFactoryImpl(domainGroup).theGroupHandlerFactory();

    GroupMonitorImpl impl = new GroupMonitorImpl(1, domainGroup);
    impl.register();
    monitors.put(new Integer(1), impl.theGroupMonitor());

    impl = new GroupMonitorImpl(2, domainGroup);
    impl.register();
    monitors.put(new Integer(2), impl.theGroupMonitor());

    impl = new GroupMonitorImpl(3, domainGroup);
    impl.register();
    monitors.put(new Integer(3), impl.theGroupMonitor());

    chat = new ChatMember(factory, printer);
    domainGroup.registerSubgroup(4, chat.theGroupMember());

    if (groupName!=null)
    {
      GroupMembershipNamingService service = GroupMembershipNamingServiceFactory.load();
      if (service==null)
      {
        printer.print("GroupMembershipNamingService is not available");
        System.exit(0);
      }
      else
      {
        toStore=service.findAndJoinGroup(groupName, ghFactory, name, null);
      }
    }
    else if (connectFile==null)
    {
      toStore=GroupMembershipBasicServiceImpl.utilCreateGroup(ghFactory);
      if (toStore==null)
      {
        printer.print("Error, group not created");
        System.exit(0);
      }
    }
    else
    {
      toStore=GroupMembershipBasicServiceImpl.utilJoinGroup(ObjectsHandling.readObject(connectFile), ghFactory);
      if (toStore==null)
      {
        printer.print("Error, member not added to group on " + connectFile);
        System.exit(0);
      }
    }
    if (refFile!=null)
      ObjectsHandling.writeObject(toStore, refFile);
  }


  public boolean handleError(String area, String reason)
  {
    printer.print("ERROR on "+area+": " + reason);
    return false;
  }

  public boolean handleException(String area, Exception exception)
  {
    exception.printStackTrace();
    return handleError(area,exception.toString());
  }

  //*************************************************************************************
  //**************************** READ ARGS **********************************************
  //*************************************************************************************

  /**
    * Reads the command line arguments, and initializes the
    * Configuration singleton class; arguments are translated into the Parameter class and
    * returned.
    * It returns null if arguments are incorrect or they do not require any GMNS processing
    **/
  public static Parameters readArgs(String args[])
  {
    Parameters ret = null;
    try
    {
      Vector params=new Vector();
      params.add(CONF);
      params.add(GROUP);
      params.add(NAME);
      params.add(CONNECT);
      params.add(REFFILE);
      params.add(HELP);

      ret = new Parameters(args, params, null,0,0);
      if (ret.hasParameter(HELP))
      {
        ret=null;
        help();
      }
      else
      {
        if (ret.hasParameter(CONF))
          Configuration.getSingleton(ret.getParameter(CONF), ret.getDefinitions());
        else
          Configuration.getSingleton(ret.getDefinitions());
        if (ret.hasParameter(GROUP))
          if (ret.hasParameter(CONNECT))
            throw new ParameterException(CONNECT + " option not compatible with " + GROUP);
          else if (ret.hasParameter(REFFILE))
            throw new ParameterException(REFFILE + " option not compatible with " + GROUP);
      }
    }
    catch (ParameterException pex)
    {
      System.err.println("Arguments error: " + pex.getMessage());
      help();
    }
    catch (IOException ioex)
    {
      System.err.println("Error reading the configuration file: " + ioex.getMessage());
    }
    return ret;
  }

  //*************************************************************************************
  //**************************** HELP ***************************************************
  //*************************************************************************************

  static void help()
  {
      System.out.println("arguments: [options...]\n\tOptions:\n\t\t"
        + CONF + "=configuration file\n\t\t\t--> sensei properties file \n\t\t"
        + GROUP + "=group name\n\t\t\t--> group name if a GMNS service is running \n\t\t"
        + NAME + "=name\n\t\t\t--> Identifies this monitor (logging & GMNS) \n\t\t"
        + CONNECT + "=group member reference file\n\t\t\t--> file with the reference of a group member to join \n\t\t"
        + REFFILE + "=reference file\n\t\t\t--> file to include the reference of this server\n\t\t"
        + HELP + "\n\t\t\t--> shows this brief help \n\n\t"
      );
  }

  //*************************************************************************************
  //**************************** DATA MEMBERS *******************************************
  //*************************************************************************************

  static final String CONF      = "config";
  static final String GROUP     = "group";
  static final String NAME      = "name";
  static final String CONNECT   = "connect";
  static final String REFFILE   = "reffile";
  static final String HELP      = "help";

}