package senseiTests.domainsTest;

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import javax.swing.*;
import sensei.util.*;
import sensei.util.logging.*;
import sensei.domains.DomainGroupHandlerImpl;
import sensei.domains.DynamicSubgroupInfoAsStringImpl;
import sensei.middleware.gms.*;
import sensei.middleware.gmns.*;
import sensei.middleware.util.*;
import sensei.gms.*;
import sensei.gmns.*;
import sensei.middleware.domains.BehaviourOnViewChanges;
import sensei.middleware.domains.DomainGroupHandler;
import sensei.middleware.domains.DomainGroupUserBaseImpl;
import sensei.middleware.domains.MemberProperties;
import sensei.middleware.domains.MemberStateException;
import sensei.middleware.domains.PropertiesDisabledException;
import sensei.middleware.domains.Property;
import sensei.middleware.domains.State;
import sensei.middleware.domains.SubgroupsHandlerException;
import sensei.middleware.domains.SubgroupsHandlerExceptionReason;
import senseiTests.middleware.domainsTest.ColourDynamicSubgroupInfo;
import senseiTests.middleware.domainsTest.Factory;
import senseiTests.middleware.domainsTest.StateTransferType;

public class Tester implements UIUser, ColoursSubgroupUser, Runnable
{
  public Tester(Parameters parameters)  throws ParameterException
  {
    this.parameters=parameters;
    elector=null;
    properties=parameters.hasParameter(PROPERTIES);

    if (parameters.getDefinitionsLength()>0)
    {
      if (!properties)
        throw new ParameterException(PROPERTIES, ":must be set to specify property definitions");
      if (parameters.hasDefinition(NAME))
      {
        name=parameters.getDefinition(NAME);
        Logger.setLogName(name);
      }
    }
    refFile = parameters.hasParameter(REFFILE)? parameters.getParameter(REFFILE) : null;
    connectFile = parameters.hasParameter(CONNECT)? parameters.getParameter(CONNECT) : null;

    answerer = new Answerer(getDelay(parameters));
    display = new UIFrame(this, answerer);
    dynamicRemovalInfo = new DynamicSubgroupInfoAsStringImpl(name);
  }

  public void run()
  {
    try
    {
      factory = new senseiTests.middleware.domainsTest.Factory();
      domainGroup = new DomainGroupHandlerImpl(). theDomainGroupHandler();
      initProperties();
      initSubgroups();

      if (parameters.hasParameter(JOIN))
        display.doJoin();
    }
    catch(Exception ex)
    {
      display.showError(ex);
      System.exit(0);
    }
  }

  public void reset()
  {
    try
    {
      domainGroup = new DomainGroupHandlerImpl(). theDomainGroupHandler();
      display.clearProperties();
      initProperties();
      initSubgroups();
    }
    catch(Exception ex)
    {
      display.showError(ex);
      System.exit(0);
    }
  }

  void initProperties()  throws ParameterException, Exception
  {
    TesterPropertiesUser propertiesUser = new TesterPropertiesUser(domainGroup, parameters.getDefinitions());
    propertiesUser.setDisplay(display);
    domainGroup.setDomainGroupUser(new TesterDomainGroupUser (this, display, propertiesUser).theDomainGroupUser());
    display.clear(propertiesUser, getBehaviour(parameters), getCEMode(parameters, properties),
      parameters.hasParameter(AUTOMATIC), parameters.hasParameter(SHOWEVENTS), properties,
      parameters.hasParameter(DYNAMIC));
  }

  void initSubgroups()  throws ParameterException, Exception
  {
    IntWrapper initialSubgroups[]=getInitialSubgroups(SUBGROUPSSH, parameters);
    if (initialSubgroups!=null)
      for (int i=0;i<initialSubgroups.length;i++)
        doCreateSubgroup(initialSubgroups[i].i, StateTransferType.StateHandler);

    initialSubgroups=getInitialSubgroups(SUBGROUPSBSH, parameters);
    if (initialSubgroups!=null)
      for (int i=0;i<initialSubgroups.length;i++)
        doCreateSubgroup(initialSubgroups[i].i, StateTransferType.BasicStateHandler);

    initialSubgroups=getInitialSubgroups(SUBGROUPSECHP, parameters);
    if (initialSubgroups!=null)
      for (int i=0;i<initialSubgroups.length;i++)
        doCreateSubgroup(initialSubgroups[i].i, StateTransferType.ExtendedCheckpointable);

    initialSubgroups=getInitialSubgroups(SUBGROUPSCHP, parameters);
    if (initialSubgroups!=null)
      for (int i=0;i<initialSubgroups.length;i++)
        doCreateSubgroup(initialSubgroups[i].i, StateTransferType.Checkpointable);

    initialSubgroups=getInitialSubgroups(SUBGROUPSNOST, parameters);
    if (initialSubgroups!=null)
      for (int i=0;i<initialSubgroups.length;i++)
        doCreateSubgroup(initialSubgroups[i].i, StateTransferType.StatelessTransfer);
  }

  int getDelay(Parameters parameters) throws ParameterException
  {
    int ret=0;
    if (parameters.hasParameter(DELAY))
    {
      String delay=null;
      try
      {
        delay = parameters.getParameter(DELAY);
        ret=Integer.valueOf(delay).intValue();
        if (ret<0)
          throw new ParameterException(DELAY, " :negative values are senseless");
      }
      catch(NumberFormatException nex)
      {
        throw new ParameterException(DELAY, " " +delay+" is not a valid integer format");
      }
    }
    return ret;
  }

  IntWrapper[] getInitialSubgroups(String type, Parameters parameters) throws ParameterException
  {
    IntWrapper[] ret = null;
    if (parameters.hasParameter(type))
    {
      String param = parameters.getParameter(type);
      int n=0,c=0;
      while(n!=-1)
      {
        n=param.indexOf('-',n);
        if (n!=-1)
        {
          ++c;
          ++n;
        }
      }
      ret=new IntWrapper[c+1];
      n=c=0;
      try
      {
        while(n!=-1)
        {
          int m=n;
          n=param.indexOf('-',n);
          int number = (n==-1)?
            Integer.valueOf(param.substring(m)).intValue() : Integer.valueOf(param.substring(m, n++)).intValue();
          if (number<=sensei.gms.Consts.INVALID_MEMBER_ID)
            throw new ParameterException(type, ":subgroups must be positive, non zero");
          ret[c++]=new IntWrapper(number);
        }
      }
      catch(NumberFormatException nex)
      {
        throw new ParameterException(type, ":value's format invalid");
      }
      catch(IndexOutOfBoundsException iobex)
      {
        throw new ParameterException(type, ":value's format invalid");
      }
    }
    return ret;
  }

  BehaviourOnViewChanges getBehaviour(Parameters parameters) throws ParameterException
  {
    BehaviourOnViewChanges ret;
    String mode = parameters.getParameter(MODE, "ME");
    if (mode.compareToIgnoreCase("me")==0)
      ret=BehaviourOnViewChanges.MembersOnTransferExcludedFromGroup;
    else if (mode.compareToIgnoreCase("nme")==0)
      ret=BehaviourOnViewChanges.StatelessMembersDoNotBelongToGroup;
    else if (mode.compareToIgnoreCase("nmb")==0)
      ret=BehaviourOnViewChanges.StatelessMembersBelongToGroup;
    else
      throw new ParameterException(MODE,":not valid (ME/NME/NMB)");
    return ret;
  }

  CoordinatorElectionMode getCEMode(Parameters parameters, boolean properties) throws ParameterException
  {
    CoordinatorElectionMode ret;
    String mode = parameters.getParameter(COORDINATORELECTION, "inf");
    if (mode.compareToIgnoreCase("inf")==0)
      ret=CoordinatorElectionMode.InfrastructureControlled;
    else if (mode.compareToIgnoreCase("user")==0)
      ret=CoordinatorElectionMode.SelectedByUser;
    else if (mode.compareToIgnoreCase("weight")==0)
      if (properties)
        ret=CoordinatorElectionMode.SelectedByWeights;
      else
        throw new ParameterException(COORDINATORELECTION,":to use weight, properties must be enabled");
    else
      throw new ParameterException(COORDINATORELECTION,":not valid (inf/weight/user)");
    return ret;
  }

  //*************************************************************************************
  //**************************** UI OPERATIONS ******************************************
  //*************************************************************************************



  public void changedCoordinatorElection(boolean toWeights)
  {
    elector.changeToUseWeights(toWeights);
  }

  public boolean doJoin(BehaviourOnViewChanges mode, CoordinatorElectionMode ceMode, boolean dynamicSubgroups)
  {
    boolean ret=true;
    try
    {
      domainGroup.setBehaviourMode(mode);
      if (dynamicSubgroups)
      {
        domainGroup.setDynamicSubgroupsUser(new TesterDynamicsGroupsUser(this, display).theDynamicSubgroupsUser());
      }

      if (ceMode!=CoordinatorElectionMode.InfrastructureControlled)
      {
        elector = new TestCoordinatorElector(ceMode==CoordinatorElectionMode.SelectedByWeights, domainGroup, display);
        domainGroup.setCoordinatorElector(elector.theCoordinatorElector());
      }

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

      if (parameters.hasParameter(GROUP))
      {
        GroupMembershipNamingService service = GroupMembershipNamingServiceFactory.load();
        if (service==null)
        {
          display.showError("GroupMembershipNamingService is not available");
          ret=false;
        }
        else
        {
          toStore = service.findAndJoinGroup(parameters.getParameter(GROUP), factory, name, null);
        }
      }
      else if (connectFile==null)
      {
        toStore=GroupMembershipBasicServiceImpl.utilCreateGroup(factory);
        if (toStore==null)
        {
          display.showError("Error, group not created");
          ret=false;
        }
      }
      else
      {
        toStore=GroupMembershipBasicServiceImpl.utilJoinGroup(ObjectsHandling.readObject(connectFile), factory);
        if (toStore==null)
        {
          display.showError("Error, member not added to group on " + connectFile);
          ret=false;
        }
      }
      if (refFile!=null && toStore!=null)
        ObjectsHandling.writeObject(toStore, refFile);
    }
    catch(Exception ex)
    {
      display.showError(ex);
      ret=false;
    }
    return ret;
  }

  public void doLeave()
  {
    try
    {
      domainGroup.leaveGroup();
    }
    catch(Exception ex)
    {
      display.showError(ex);
      System.exit(0);
    }
  }

  public void doCreateSubgroup(int id, StateTransferType type)
  {
    try
    {
      if (type==StateTransferType.StateHandler)
      {
        ColoursSubgroupStateHandler subgroup = new ColoursSubgroupStateHandler(null, this, factory, answerer);
        domainGroup.registerSubgroup(id, subgroup.theStateHandler());
        display.createColourPanel(new IntWrapper(id), type, new StateChanger(domainGroup, subgroup));
        subgroup.setIdentity(id);
      }
      else if (type==StateTransferType.BasicStateHandler)
      {
        ColoursSubgroupBasicStateHandler subgroup = new ColoursSubgroupBasicStateHandler(null, this, factory, answerer);
        domainGroup.registerSubgroup(id, subgroup.theBasicStateHandler());
        display.createColourPanel(new IntWrapper(id), type, new StateChanger(domainGroup, subgroup));
        subgroup.setIdentity(id);
      }
      else if (type==StateTransferType.StatelessTransfer)
      {
        ColoursSubgroupNoST subgroup = new ColoursSubgroupNoST(null, this, factory, answerer);
        domainGroup.registerSubgroup(id, subgroup.theGroupMember());
        display.createColourPanel(new IntWrapper(id), type, new StateChanger(domainGroup, subgroup));
        subgroup.setIdentity(id);
      }
      else if (type==StateTransferType.ExtendedCheckpointable)
      {
        ColoursSubgroupExtendedCheckpointable subgroup = new ColoursSubgroupExtendedCheckpointable(null, this, factory, answerer);
        domainGroup.registerSubgroup(id, subgroup.theExtendedCheckpointable());
        display.createColourPanel(new IntWrapper(id), type, new StateChanger(domainGroup, subgroup));
        subgroup.setIdentity(id);
      }
      else
      {
        ColoursSubgroupCheckpointable subgroup = new ColoursSubgroupCheckpointable(null, this, factory, answerer);
        domainGroup.registerSubgroup(id, subgroup.theCheckpointable());
        display.createColourPanel(new IntWrapper(id), type, new StateChanger(domainGroup, subgroup));
        subgroup.setIdentity(id);
      }
    }
    catch (SubgroupsHandlerException ex)
    {
      if (ex.reason==SubgroupsHandlerExceptionReason.SubgroupIdAlreadyInUse)
        display.showMessage("Subgroup ID is already in use: "+id);
      else if (ex.reason==SubgroupsHandlerExceptionReason.InvalidStaticSubgroupId)
        display.showMessage("Subgroup ID is not valid as static: "+id);
      else
        display.showError("Could not register subgroup " + id);
    }
    catch (Exception ex)
    {
      display.showError(ex);
    }
  }

  public void doRemoveSubgroup(int id)
  {
    try
    {
      domainGroup.removeSubgroup(id, dynamicRemovalInfo);
    }
    catch(MemberStateException mex)
    {
      display.showMessage("MemberStateException: " + mex.toString());
    }
    catch (SubgroupsHandlerException ex)
    {
      if (ex.reason==SubgroupsHandlerExceptionReason.InvalidDynamicSubgroupId)
        display.showMessage("Subgroup ID is not a dynamic one: "+id);
      else
        display.showError("Could not remove subgroup " + id);
    }
    catch (Exception ex)
    {
      display.showError(ex);
    }
  }

  public void doCreateDynamicSubgroup(String initialStates[], StateTransferType type, boolean block)
  {
    try
    {
      ColourDynamicSubgroupInfo info = factory.createColourDynamicSubgroupInfo(name, type, initialStates);
      if (block)
      {
        GroupMember groupMember;
        ColoursSubgroup subgroup;
        if (type==StateTransferType.StateHandler)
        {
          ColoursSubgroupStateHandler subgroupSH = new ColoursSubgroupStateHandler(domainGroup, this, factory, answerer);
          subgroup = subgroupSH;
          groupMember = subgroupSH.theStateHandler();
        }
        else if (type==StateTransferType.BasicStateHandler)
        {
          ColoursSubgroupBasicStateHandler subgroupSH = new ColoursSubgroupBasicStateHandler(domainGroup, this, factory, answerer);
          subgroup = subgroupSH;
          groupMember = subgroupSH.theBasicStateHandler();
        }
        else if (type==StateTransferType.StatelessTransfer)
        {
          ColoursSubgroupNoST subgroupNoST = new ColoursSubgroupNoST(domainGroup, this, factory, answerer);
          subgroup = subgroupNoST;
          groupMember = subgroupNoST.theGroupMember();
        }
        else if (type==StateTransferType.ExtendedCheckpointable)
        {
          ColoursSubgroupExtendedCheckpointable subgroupNoST = new ColoursSubgroupExtendedCheckpointable(domainGroup, this, factory, answerer);
          subgroup = subgroupNoST;
          groupMember = subgroupNoST.theExtendedCheckpointable();
        }
        else
        {
          ColoursSubgroupCheckpointable subgroupNoST = new ColoursSubgroupCheckpointable(domainGroup, this, factory, answerer);
          subgroup = subgroupNoST;
          groupMember = subgroupNoST.theCheckpointable();
        }
        int id = domainGroup.createSubgroup(info, groupMember);
        if (id==sensei.domains.Consts.INVALID_MEMBER_ID)
          display.showMessage("Error creating the dynamic subgroup");
        else
        {
          IntWrapper idW = new IntWrapper(id);
          display.createColourPanel(idW, type, new StateChanger(domainGroup, subgroup));
          subgroup.setIdentity(id);
          subgroup.setState(initialStates);
          display.enableUserInteractionOnColourPanel(idW);
        }
      }
      else
      {
        domainGroup.castSubgroupCreation(info);
      }
    }
    catch (SubgroupsHandlerException ex)
    {
      if (ex.reason==SubgroupsHandlerExceptionReason.DynamicBehaviourNotRegistered)
        display.showMessage("Dynamic subgroups are not allowed");
      else
        display.showError("Could not create subgroup :" + ex.toString());
    }
    catch (Exception ex)
    {
      display.showError(ex);
    }
  }

  public GroupMember subgroupCreated(int id, StateTransferType type, String[]state)
  {
    GroupMember ret=null;
    try
    {
      ColoursSubgroup subgroup;
      if (type==StateTransferType.StateHandler)
      {
        ColoursSubgroupStateHandler subgroupSH = new ColoursSubgroupStateHandler(domainGroup, this, factory, answerer);
        subgroup = subgroupSH;
        ret = subgroupSH.theStateHandler();
      }
      else if (type==StateTransferType.BasicStateHandler)
      {
        ColoursSubgroupBasicStateHandler subgroupSH = new ColoursSubgroupBasicStateHandler(domainGroup, this, factory, answerer);
        subgroup = subgroupSH;
        ret = subgroupSH.theBasicStateHandler();
      }
      else if (type==StateTransferType.StatelessTransfer)
      {
        ColoursSubgroupNoST subgroupNoST = new ColoursSubgroupNoST(domainGroup, this, factory, answerer);
        subgroup = subgroupNoST;
        ret = subgroupNoST.theGroupMember();
      }
      else if (type==StateTransferType.ExtendedCheckpointable)
      {
        ColoursSubgroupExtendedCheckpointable subgroupNoST = new ColoursSubgroupExtendedCheckpointable(domainGroup, this, factory, answerer);
        subgroup = subgroupNoST;
        ret = subgroupNoST.theExtendedCheckpointable();
      }
      else
      {
        ColoursSubgroupCheckpointable subgroupNoST = new ColoursSubgroupCheckpointable(domainGroup, this, factory, answerer);
        subgroup = subgroupNoST;
        ret = subgroupNoST.theCheckpointable();
      }
      IntWrapper idW = new IntWrapper(id);
      display.createColourPanel(idW, type, new StateChanger(domainGroup, subgroup));
      subgroup.setIdentity(id);
      if (state!=null)
      {
        subgroup.setState(state);
        display.enableUserInteractionOnColourPanel(idW);
      }
    }
    catch(Exception ex)
    {
      display.showError(ex);
    }
    return ret;
  }

  public void onExit()
  {
    System.exit(0);
  }

  public void changedState(int subgroup, String state)
  {
    display.setStateOnColourPanel(subgroup, state);
  }

  //*************************************************************************************
  //**************************** 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(CONNECT);
      params.add(REFFILE);
      params.add(JOIN);
      params.add(SUBGROUPSNOST);
      params.add(SUBGROUPSCHP);
      params.add(SUBGROUPSECHP);
      params.add(SUBGROUPSBSH);
      params.add(SUBGROUPSSH);
      params.add(SHOWEVENTS);
      params.add(DYNAMIC);
      params.add(PROPERTIES);
      params.add(AUTOMATIC);
      params.add(DELAY);
      params.add(MODE);
      params.add(COORDINATORELECTION);
      params.add(HELP);

      ret = new Parameters(args, params, null,0,0);
      if (ret.hasParameter(HELP))
      {
        help();
        ret=null;
      }
      else
      {
        if (ret.hasParameter(CONF))
          Configuration.getSingleton(ret.getParameter(CONF), ret.getDefinitions());
        else
          Configuration.getSingleton(ret.getDefinitions());
      }
    }
    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...] [properties] \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--> GMNS name \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"
        + SUBGROUPSNOST + "= subgroup1-subgroup2... \n\t\t\t--> initial subgroups without ST to create\n\t\t"
        + SUBGROUPSCHP + "= subgroup1-subgroup2... \n\t\t\t--> initial subgroups Checkpointable to create\n\t\t"
        + SUBGROUPSECHP + "= subgroup1-subgroup2... \n\t\t\t--> initial subgroups ExtendedCheckpointable to create\n\t\t"
        + SUBGROUPSBSH + "= subgroup1-subgroup2... \n\t\t\t--> initial subgroups BasicStateHandler to create\n\t\t"
        + SUBGROUPSSH + "= subgroup1-subgroup2... \n\t\t\t--> initial subgroups StateHandler to create\n\t\t"
        + MODE + "= ME(members excluded)\n\t\t\t/NME(new members excluded)\n\t\t\t/NMB(new members belong)\n\t\t"
        + COORDINATORELECTION + "= inf(infrastrure controlled)\n\t\t\t/weight(selected on weights)" +
                    "\n\t\t\t/user(selected by the user)\n\t\t"
        + DYNAMIC + "\n\t\t\t--> enables dynamic subgroups\n\t\t"
        + JOIN + "\n\t\t\t--> joins/creates inmediately the group\n\t\t"
        + AUTOMATIC + "\n\t\t\t--> starts on automatic mode\n\t\t"
        + DELAY + "= time in milliseconds \n\t\t\t--> sets a delay to automatic mode\n\t\t"
        + SHOWEVENTS + "\n\t\t\t--> shows the events window\n\t\t"
        + PROPERTIES + "\n\t\t\t--> enables the properties\n\t\t"
        + HELP + "\n\t\t\t--> shows this brief help \n\n\t"
        + "Properties: defined as -Dkey=value. name & weight have special meaning."
      );
  }

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

  Parameters parameters;
  DomainGroupHandler domainGroup;
  String refFile, connectFile;
  UIFrame display;
  Answerer answerer;
  TestCoordinatorElector elector;
  Factory factory;
  String name="unknown";
  DynamicSubgroupInfoAsStringImpl dynamicRemovalInfo;
  boolean properties;

  static final String CONF      = "config";
  static final String GROUP      = "group";
  static final String CONNECT   = "connect";
  static final String MODE      = "mode";
  static final String COORDINATORELECTION = "coordinator";
  static final String JOIN      = "join";
  static final String REFFILE   = "reffile";
  static final String SUBGROUPSNOST = "subgroupsNoST";
  static final String SUBGROUPSCHP  = "subgroupsChP";
  static final String SUBGROUPSECHP  = "subgroupsEChP";
  static final String SUBGROUPSBSH  = "subgroupsBSH";
  static final String SUBGROUPSSH   = "subgroupsSH";
  static final String AUTOMATIC = "automatic";
  static final String DELAY     = "delay";
  static final String SHOWEVENTS= "events";
  static final String PROPERTIES= "properties";
  static final String DYNAMIC   = "dynamic";
  static final String NAME      = "name";
  static final String HELP      = "help";
}