/*
 * This file is part of JGAP.
 *
 * JGAP offers a dual license model containing the LGPL as well as the MPL.
 *
 * For licensing information please see the file license.txt included with JGAP
 * or have a look at the top of class org.jgap.Chromosome which representatively
 * includes the JGAP license policy applicable for any file delivered with JGAP.
 */
package org.jgap.distr.grid.gp;

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

import org.apache.commons.cli.*;
import org.homedns.dade.jcgrid.client.*;
import org.homedns.dade.jcgrid.cmd.*;
import org.homedns.dade.jcgrid.message.*;
import org.jgap.*;
import org.jgap.distr.*;
import org.jgap.distr.grid.*;
import org.jgap.distr.grid.common.*;
import org.jgap.distr.grid.util.*;
import org.jgap.distr.grid.wan.*;
import org.jgap.gp.*;
import org.jgap.gp.impl.*;
import org.jgap.util.*;

import com.thoughtworks.xstream.io.xml.*;

/**
 * A client defines work for the grid and sends it to the JGAPServer.
 * Use this class as base class for your grid client implementations.
 *
 * @author Klaus Meffert
 * @since 3.2
 */
public class JGAPClientGP
    extends Thread {
  /**@todo in dateiname requester/worker kodieren*/
  /**@todo auch schlechtere ergebnisse einmixen: die direkt empfangenen
   * gleich wieder in einen request reinstecken --> aber mischen verschiedener
   * results in einen request!*/

  /**@todo small, medium, large work requests*/
  /**@todo re-evaluate each result on behalf of another worker: keep separate
   * lookup-table for all requests --> m_resultsVerified, m_resultsPersister */
  /**@todo remove old requests from online store automatically*/
  /**@todo info when work request has been taken*/
  /**@todo info when worker logs on --> evaluate logon files*/
  /**@todo top results in eigener datei speichern,
   * komprimierung durch weglassen uberfl. infos, siehe xml --> injection after reload*/
  /**@todo copy good results to online folder*/

  /** String containing the CVS revision. Read out via reflection!*/
  private final static String CVS_REVISION = "$Revision: 1.19 $";

  public static final String APP_VERSION = "1.02a";

  /**@todo store version in external file*/

  public static final String MODULE_CS = "CS";

  public static final String CLIENT_DATABASE = "clientdbGP.jgap";

  public static final String RESULTS_DATABASE = "results.jgap";

  /**@todo das ist nicht module, sondern sender-receiver*/

  public static final String MODULE_SC = "SC";

  public static final String MODULE_SW = "SW";

  public static final String MODULE_WS = "WS";

  public static final String MODULE_ANY = "*";

  public static final String CONTEXT_WORK_REQUEST = "WREQ";

  public static final String CONTEXT_WORK_RESULT = "WRES";

  public static final String CONTEXT_ANY = "W*";

  public static final String CONTEXT_ID_EMPTY = "0";

  public static final String CONTEXT_ID_ANY = "*";

  public static final int TIMEOUT_SECONDS = 20;

  public static final int WAITTIME_SECONDS = 5;

  public static final Object[][] FIELDSTOSKIP = new Object[][] { {GPPopulation.class,
      "m_fitnessRank"}, {GPPopulation.class, "m_fittestProgram"}, {GPPopulation.class,
      "m_changed"}, {GPPopulation.class, "m_sorted"}, {GPPopulation.class,
      "m_fittestToAdd"}, {BaseGPChromosome.class,
      "m_ind"},
  };

  private static transient org.apache.log4j.Logger log
      = org.apache.log4j.Logger.getLogger(JGAPClientGP.class);

  protected GridNodeClientConfig m_gridconfig;

  protected JGAPRequestGP m_workReq;

  private IGridClientMediator m_gcmed;

  private IGridConfigurationGP m_gridConfig;

  /**
   * Is the client operating in a WAN or in a LAN?
   * TRUE:  WAN --> Do not use JCGrid architecture
   * FALSE: LAN --> Do use JCGrid architecture
   */
  private boolean m_WANMode;

  /**
   * Only received results or send requests beforehand?
   */
  private boolean m_receiveOnly;

  private boolean m_list;

  private String m_workDir;

  /**
   * Dir for not too bad results
   */
  private String m_ntbResultsDir;

  private String m_runID;

  private boolean m_endless;

  private ClientStatus m_objects;

  private PersistableObject m_persister;

  private ResultVerification m_resultsVerified;

  private PersistableObject m_resultsPersister;

  private int m_requestIdx;

  private boolean m_no_comm;

  private boolean m_no_evolution;

  private int m_max_fetch_results;

  public JGAPClientGP(GridNodeClientConfig a_gridconfig,
                      String a_clientClassName,
                      boolean a_WANMode,
                      boolean a_receiveOnly, boolean a_list, boolean a_no_comm,
                      boolean a_no_evolution, boolean a_endless,
                      int a_max_fetch_results)
      throws Exception {
    this(null, a_gridconfig, a_clientClassName, a_WANMode, a_receiveOnly,
         a_list, a_no_comm, a_no_evolution, a_endless, a_max_fetch_results);
    m_gcmed = new DummyGridClientMediator(m_gridconfig);
  }

  public JGAPClientGP(IGridClientMediator a_gcmed,
                      GridNodeClientConfig a_gridconfig,
                      String a_clientClassName, boolean a_WANMode,
                      boolean a_receiveOnly, boolean a_list, boolean a_no_comm,
                      boolean a_no_evolution, boolean a_endless,
                      int a_max_fetch_results)
      throws Exception {
    log.info("This is JGAP Grid version " + APP_VERSION);
    m_runID = getRunID();
    log.info("ID of this run: " + m_runID);
    if (a_clientClassName == null || a_clientClassName.length() < 1) {
      throw new IllegalArgumentException(
          "Please specify a class name of the configuration!");
    }
    m_WANMode = a_WANMode;
    m_receiveOnly = a_receiveOnly;
    if (m_receiveOnly) {
      log.info("Only receive results");
    }
    m_list = a_list;
    if (m_list) {
      log.info("List requests and results");
    }
    m_endless = a_endless;
    if (m_endless) {
      log.info("Endless run");
    }
    m_no_comm = a_no_comm;
    if (m_no_comm) {
      log.info("Don't send or receive anything");
    }
    m_no_evolution = a_no_evolution;
    if (m_no_evolution) {
      log.info("Don't execute genetic evolution");
    }
    m_max_fetch_results = a_max_fetch_results;
    if (m_max_fetch_results <= 0) {
      m_max_fetch_results = 500;
    }
    log.info("Maximum number of results to fetch at once: " +
             m_max_fetch_results);
    m_gridconfig = a_gridconfig;
    Class client = Class.forName(a_clientClassName);
    m_gridConfig = (IGridConfigurationGP) client.getConstructor(new
        Class[] {}).newInstance(new Object[] {});
    m_gridConfig.initialize(m_gridconfig);
    if (m_gridConfig.getClientFeedback() == null) {
      m_gridConfig.setClientFeedback(new NullClientFeedbackGP());
    }
    // Setup work request.
    // -------------------
    /**@todo ab 2. zyklus ist pop.grosse nur 1 !*/
    JGAPRequestGP req = new JGAPRequestGP(m_gridconfig.getSessionName(),
                                          m_runID + "_" + m_requestIdx, 0,
                                          m_gridConfig);
    m_requestIdx++;
    req.setWorkerReturnStrategy(m_gridConfig.getWorkerReturnStrategy());
    req.setGenotypeInitializer(m_gridConfig.getGenotypeInitializer());
    req.setEvolveStrategy(m_gridConfig.getWorkerEvolveStrategy());
    MasterInfo requester = new MasterInfo(true);
    req.setRequesterInfo(requester);
    req.setRequestDate(DateKit.now());
    // If evolution takes place on client only:
    // ----------------------------------------
//    req.setEvolveStrategy(null);
    //
    setWorkRequest(req);
    m_gcmed = a_gcmed;
    init();
  }

  private void init()
      throws Exception {
    if (getWorkDirectory() == null) {
      String workDir = FileKit.getCurrentDir() + "/work/" + "storage";
      workDir = FileKit.getConformPath(workDir);
      setWorkDirectory(workDir);
    }
    m_ntbResultsDir = FileKit.addSubDir(getWorkDirectory(), "ntb", true);
    FileKit.createDirectory(m_ntbResultsDir);
    log.info("NTB dir: " + m_ntbResultsDir);
//    m_workDirResults =FileKit.getCurrentDir() + "/work/" + "results";
//    m_workDirResults = FileKit.getConformPath(workDir);
    // Try to load previous object information.
    // ----------------------------------------
    File f = new File(getWorkDirectory(), CLIENT_DATABASE);
    m_persister = new PersistableObject(f);
    m_objects = (ClientStatus) m_persister.load();
    if (m_objects == null) {
      m_objects = new ClientStatus();
      m_persister.setObject(m_objects);
    }
    // Try to load previous request information.
    // -----------------------------------------
    f = new File(getWorkDirectory(), RESULTS_DATABASE);
    m_resultsPersister = new PersistableObject(f);
    m_resultsVerified = (ResultVerification) m_resultsPersister.load();
    if (m_resultsVerified == null) {
      m_resultsVerified = new ResultVerification();
      m_resultsPersister.setObject(m_resultsVerified);
    }
  }

  /**
   * @return the most possibly unique ID of a single program execution
   *
   * @author Klaus Meffert
   * @since 3.3.3
   */
  protected String getRunID() {
    if (m_runID == null) {
      return "RJGrid" + DateKit.getNowAsString();
    }
    else {
      return m_runID;
    }
  }

  public void setWorkRequest(JGAPRequestGP a_request) {
    m_workReq = a_request;
  }

  /**
   * Called at start of run().
   * Override in sub classes if needed.
   *
   * @throws Exception
   *
   * @author Klaus Meffert
   * @since 3.3.3
   */
  protected void onBeginOfRunning()
      throws Exception {
  }

  /**
   * Called in run() before sending work requests.
   * Override in sub classes if needed.
   *
   * @param a_workRequests work requests pending to be sent
   *
   * @return true: do send work requests, false: don't send any work request
   *
   * @throws Exception
   *
   * @author Klaus Meffert
   * @since 3.3.3
   */
  protected boolean beforeSendWorkRequests(JGAPRequestGP[] a_workRequests)
      throws Exception {
    return true;
  }

  /**
   * Called in run() before generating work requests for sending.
   * Override in sub classes if needed.
   *
   * @return true: do generate work requests, false: don't generate any work
   * request
   *
   * @throws Exception
   *
   * @author Klaus Meffert
   * @since 3.3.3
   */
  protected boolean beforeGenerateWorkRequests()
      throws Exception {
    return true;
  }

  /**
   * Called in run() after sending work requests successfully.
   * Override in sub classes if needed.
   *
   * @param a_workRequests the sent requests
   * @return true: process further, false: stop processing the rest
   * @throws Exception
   *
   * @author Klaus Meffert
   * @since 3.3.3
   */
  protected boolean afterSendWorkRequests(JGAPRequestGP[] a_workRequests)
      throws Exception {
    return true;
  }

  protected void errorOnSendWorkRequests(Throwable uex,
      JGAPRequestGP[] a_workRequests)
      throws Exception {
  }

  /**
   * Called in run() before one evolution step is executed.
   * Override in sub classes if needed.
   *
   * @param a_gcmed the GridClient mediator
   *
   * @throws Exception
   *
   * @author Klaus Meffert
   * @since 3.3.3
   */
  protected void beforeEvolve(IGridClientMediator a_gcmed)
      throws Exception {
  }

  /**
   * Called in run() after one evolution step is executed.
   * Override in sub classes if needed.
   *
   * @param a_gcmed the GridClient mediator
   *
   * @throws Exception
   *
   * @author Klaus Meffert
   * @since 3.3.3
   */
  protected void afterEvolve(IGridClientMediator a_gcmed)
      throws Exception {
  }

  /**
   * Called after stopping the client in run().
   * Override in sub classes if needed.
   *
   * @param a_t null if no error occured on stopping, otherwise exception object
   *
   * @throws Exception
   *
   * @author Klaus Meffert
   * @since 3.3.3
   */
  protected void afterStopped(Throwable a_t)
      throws Exception {
  }

  /**
   * Called in run() in case of any unhandled error.
   * Override in sub classes if needed.
   *
   * @param a_ex exception object expressing the error
   *
   * @throws Exception
   *
   * @author Klaus Meffert
   * @since 3.3.3
   */
  protected void onError(Exception a_ex)
      throws Exception {
    // Do not handle any eror specifically.
    // ------------------------------------
    throw a_ex;
  }

  /**
   * Called in case of any unhandled error when trying to delete a request or
   * result.
   * Override in sub classes if needed.
   *
   * @param a_ex exception object expressing the error
   *
   * @throws Exception
   *
   * @author Klaus Meffert
   * @since 3.3.4
   */
  protected void onDeleteError(Exception a_ex)
      throws Exception {
    // Do not handle any eror specifically.
    // ------------------------------------
    throw a_ex;
  }

  /**
   * Called in run() on error when receiving work results.
   * Override in sub classes if needed.
   *
   * @param a_workRequests for which to receive results
   * @param a_ex Exception occured
   * @throws Exception rethrow an unhandled exception!
   *
   * @author Klaus Meffert
   * @since 3.3.4
   */
  protected void onErrorReceiveWorkResults(JGAPRequestGP[] a_workRequests,
      Exception a_ex)
      throws Exception {
    // Do not handle any eror specifically.
    // ------------------------------------
    throw a_ex;
  }

  /**
   * Threaded: Splits work, sends it to workers and receives computed solutions.
   *
   * @author Klaus Meffert
   * @since 3.01
   */
  public void run() {
    try {
      try {
        // Check for updates.
        // ------------------
        String libDir = "D:\\jgap\robocode\\rjgrid\\lib\\";
//        checkForUpdates("http://www.klaus-meffert.de/", libDir, m_workDir);
      } catch (Exception ex) {
        log.error("Check for updates failed", ex);
      }
      do {
        boolean showResultsError = false;
        do {
          boolean doBreak = false;
          try {
            onBeginOfRunning();
            if (!showResultsError) {
              // Show stats about best results for current application.
              // ------------------------------------------------------
              try {
                showCurrentResults();
              } catch (Exception ex) {
                log.error("Error during showing current results", ex);
                showResultsError = true;
              }
            }
            // Do deferred deletion of results.
            // --------------------------------
            Iterator it = m_objects.getResults().keySet().iterator();
            boolean modified = false;
            try {
              while (it.hasNext()) {
                String key = it.next();
                String value = (String) m_objects.getResults().get(key);
                if (startsWith(value, "delete:")) {
                  log.info("Delete result (deferred), key: " + key);
                  try {
                    m_gcmed.removeMessage(key);
                  } catch (MalformedURLException mex) {
                    log.warn("Invalid key", mex);
                  } catch (Exception ex) {
                    onDeleteError(ex);
                    continue;
                  }
                  it.remove();
//                  m_objects.getResults().remove(key);
                  modified = true;
                }
                else {
                  if (startsWith(value, "delete")) {
                    it.remove();
                    modified = true;
                  }
                }
              }
            } finally {
              if (modified) {
                m_persister.save();
                modified = false;
              }
            }
            try {
              try {
                if (m_list) {
                  // List existing requests and results with extended information.
                  // -------------------------------------------------------------
                  listRequests();
                  listResults();
                }
                if (!m_receiveOnly && !m_no_evolution) {
                  // Initialize evolution.
                  // ---------------------
                  IClientEvolveStrategyGP clientEvolver = m_gridConfig.
                      getClientEvolveStrategy();
                  if (clientEvolver != null) {
                    clientEvolver.initialize(m_gcmed, getConfiguration(),
                        m_gridConfig.getClientFeedback());
                  }
                }
                if (!m_no_evolution) {
                  // Do the evolution.
                  // -----------------
                  beforeEvolve(m_gcmed);
                  evolve(m_gcmed, m_receiveOnly);
                  afterEvolve(m_gcmed);
                }
                doBreak = true;
              } catch (Exception ex) {
                log.error("Error: ", ex);
                throw ex;
              }
            } finally {
              Throwable t = null;
              try {
                try {
                  m_gcmed.stop();
                } catch (Throwable t1) {
                  t = t1;
                }
              } finally {
                log.info("Calling afterStopped");
                afterStopped(t);
                if (doBreak) {
                  break;
                }
              }
            }
          } catch (Exception ex1) {
            try {
              log.info("before onError");
              onError(ex1);
            } catch (Exception ex) {
              log.fatal("Unpredicted error", ex);
              m_gridConfig.getClientFeedback().error(
                  "Error while doing the work",
                  ex);
              try {
//              m_gcmed.disconnect();
              } catch (Exception ex2) {
                log.warn("Precautios disconnect failed.", ex2);
              }
              sleep(10000);
            }
          }
        } while (true);
        if (!m_endless) {
          break;
        }
        else {
          log.info("Starting again after a short break...");
          sleep(15000);
        }
      } while (true);
    } catch (InterruptedException iex) {
      // Thread interrupted.
      // -------------------
      log.fatal("Thread was interrupted", iex);
      try {
        m_gcmed.disconnect();
      } catch (Exception ex) {
        log.warn("Disconnect after interruption failed", ex);
      }
    } catch (Throwable t) {
      t.printStackTrace();
    }
    log.info("Stopping client");
  }

  protected JGAPRequestGP[] sendWorkRequests(int a_evolutionIndex,
      IClientEvolveStrategyGP evolver, IRequestSplitStrategyGP splitter,
      IClientFeedbackGP feedback)
      throws Exception {
    JGAPRequestGP[] workRequests = null;
    if (beforeGenerateWorkRequests()) {
      log.info("Beginning evolution cycle " + a_evolutionIndex);
      try {
//      m_clientEvolveStrategy.beforeGenerateWorkResults();
        workRequests = evolver.generateWorkRequests(m_workReq, splitter, null);
        feedback.setProgressMaximum(0);
        feedback.setProgressMaximum(workRequests.length - 1);
        for (int i = 0; i < workRequests.length; i++) {
          log.info("Setting up work request " + i);
          presetPopulation(workRequests[i]);
        }
        if (beforeSendWorkRequests(workRequests)) {
          /**@todo merge previous results in req.getPopulation()*/
          if (!m_no_comm) {
            try {
              sendWorkRequests(workRequests);
              return workRequests;
            } catch (Exception ex) {
              throw new WorkRequestsSendException(ex, workRequests);
            }
          }
          else {
            return workRequests;
          }
        }
        else {
          return null;
        }
      } catch (Exception ex) {
        ex.printStackTrace();
        throw ex;
      }
    }
    else {
      return null;
    }
  }

  protected void sendWorkRequests(JGAPRequestGP[] a_workList)
      throws Exception {
    // Send work requests.
    // -------------------
    for (int i = 0; i < a_workList.length; i++) {
      JGAPRequestGP req = a_workList[i];
      req.setRequestDate(DateKit.now());
      GPPopulation pop = req.getPopulation();
      if (pop == null || pop.isFirstEmpty()) {
        log.debug("Population to send to worker is empty!");
      }
      else {
        GPGenotype.checkErroneousPop(pop, " before sending to worker", true);
        /**@todo hier ist fehler aufgetreten!*/
      }
      m_gridConfig.getClientFeedback().sendingFragmentRequest(req);
      MessageContext context = new MessageContext(MODULE_CS,
          CONTEXT_WORK_REQUEST, CONTEXT_ID_EMPTY);
      context.setVersion(APP_VERSION);
      m_gcmed.send(new GridMessageWorkRequest(req), context, null);
      if (isInterrupted()) {
        break;
      }
    }
  }

  protected void receiveWorkResults(JGAPRequestGP[] workList)
      throws Exception {
    log.info("Receiving work results...");
    IClientFeedbackGP feedback = m_gridConfig.getClientFeedback();
    // Receive work results.
    // ---------------------
    int size;
    if (workList == null) {
      size = -1;
    }
    else {
      size = workList.length;
    }
    if (m_WANMode) {
      // First, get a list of all work results.
      // --------------------------------------
      MessageContext context = new MessageContext(MODULE_WS,
          /**@todo later: SC*/
          CONTEXT_WORK_RESULT, CONTEXT_ID_EMPTY);
      List results = m_gcmed.listResults(context, null, null);
      // Then, iterate over them and receive one after another.
      // ------------------------------------------------------
      if (results == null || results.size() < 1) {
        log.info("No earlier results found.");
      }
      else {
        int i = 0;
        int len = results.size();
        log.info(len + " results found.");
        if (len > getMaxFetchResults()) {
          len = getMaxFetchResults();
          log.info("Fetching only " + len + " results.");
        }
        /**@todo sort results according to post date, the oldest first*/
        for (Object resultStub : results) {
          if (i >= m_max_fetch_results) {
            break;
          }
          feedback.setProgressValue(i);
          JGAPResultGP result = receiveWorkResult(resultStub, feedback, false);
          if (result != null) {
            log.info(" Generic data: " + result.getGenericData());
            /**@todo config.params wie popsite, evol.anz dazu*/
            log.info(" Title: " + result.getTitle());
            IGPProgram best = result.getPopulation().determineFittestProgram();
            String key = result.getID();
            // Check if result already received, and if, skip it
            if (m_objects.getResults().get(key) != null) {
              log.info("Already received result detected, key: "+key);
              continue;
            }
            if (best == null) {
              log.info("Empty result received!");
            }
            m_objects.getResults().put(key, "received");
            // Work with the result.
            // ---------------------
            m_gridConfig.getClientEvolveStrategy().resultReceived(result);
            try {
              // Remove result from online store.
              // ---------------------------------
              try {
                log.info("Removing result from online store");
                if (false && result.getGenericData() != null &&
                    WANData.class.isAssignableFrom(result.getGenericData().
                    getClass())) {
                  WANData wanData = (WANData) result.getGenericData();
                  m_gcmed.removeMessage(wanData.getUri());
                }
                else {
                  m_gcmed.removeMessage(resultStub);
                }
              } catch (Exception ex) {
                log.warn("Deletion of result failed, deferring...", ex);
                key = getKeyFromObject(resultStub);
                if (key != null) {
                  log.info(" Key for later deletion: " + key);
                  m_objects.getResults().put(key, "delete:");
                }
                else {
                  log.info("Deferred deletion not possible: key unknown");
                }
              }
            } finally {
              m_persister.save();
            }
            i++;
            resultReceived(best);
            MasterInfo worker = result.getWorkerInfo();
            if (worker != null) {
              log.info(" Worker IP " + worker.m_IPAddress + ", host " +
                       worker.m_name);
            }
            // Store result to disk if it is fit enough.
            // -----------------------------------------
            double minFitness;
            minFitness = m_gridConfig.getMinFitnessToStore();
            if (minFitness < 0.0001d) {/**@todo allow fitness 0.0*/
              minFitness = 5000;
            }
            if (best != null && best.getFitnessValue() >= minFitness) {
              String filename = getResultFilename(result);
              log.info("Writing result to file " + filename);
              writeToFile(best, m_workDir, filename);
            }
            // Now remove the result from the online store.
            // --------------------------------------------
            /**@todo do this here explicitely and not in receiveWorkResult*/
          }
        }
      }
    }
    else {
      for (int i = 0; i < size; i++) {
        feedback.setProgressValue(i + workList.length);
        receiveWorkResult(workList, feedback);
        if (this.isInterrupted()) {
          break;
        }
      }
    }
  }

  protected String getResultFilename(JGAPResultGP a_result) {
    IGPProgram fittest = WANUtils.getFittest(a_result);
    String fitness = "";
    if (fittest == null) {
      // Should not happen at all!
      log.error("No fittest program found!");
    }
    else {
      fitness = NumberKit.niceDecimalNumber(fittest.getFitnessValue(), 2);
    }
    return "result_"
        + fitness
        + "_"
        + getRunID()
        + "_"
        + a_result.getID()
        + "_"
        + a_result.getSessionName()
        + "_" + a_result.getChunk()
        + ".jgap";
  }

  private JGAPResultGP receiveWorkResult(Object a_result,
      IClientFeedbackGP feedback, boolean a_remove)
      throws Exception {
    // Object reference is realized via context id.
    // --------------------------------------------
    MessageContext context = new MessageContext(MODULE_WS /**@todo later: SC*/,
        CONTEXT_WORK_RESULT, a_result);
    GridMessageWorkResult gmwr = (GridMessageWorkResult) m_gcmed.
        getGridMessage(context, null, TIMEOUT_SECONDS, WAITTIME_SECONDS,
                       a_remove);
    if (gmwr == null) {
      throw new WorkResultNotFoundException();
    }
    else {
      String s = " ";
      if (a_remove) {
        s += "and removed from WAN";
      }
      log.info("Work result received" + s);
    }
    JGAPResultGP workResult = (JGAPResultGP) gmwr.getWorkResult();
    int idx = workResult.getChunk();
    // Fire listener.
    // --------------
    feedback.receivedFragmentResult(null, workResult, idx);
    return workResult;
  }

  private JGAPResultGP receiveWorkResult(JGAPRequestGP[] workList,
      IClientFeedbackGP feedback)
      throws Exception {
    /**@todo make this asynchronous with fall-back and reconnect!*/
    MessageContext context = new MessageContext(MODULE_WS /**@todo later: SC*/,
        CONTEXT_WORK_RESULT, CONTEXT_ID_EMPTY);
    GridMessageWorkResult gmwr = (GridMessageWorkResult) m_gcmed.
        getGridMessage(context, null, TIMEOUT_SECONDS, WAITTIME_SECONDS, true);
    if (gmwr == null) {
      throw new NoWorkResultsFoundException();
    }
    else {
      log.info("Work result received!");
    }
    JGAPResultGP workResult = (JGAPResultGP) gmwr.getWorkResult();
    m_gridConfig.getClientEvolveStrategy().resultReceived(workResult);
    int idx = workResult.getChunk();
    // Fire listener.
    // --------------
    JGAPRequestGP req;
    if (workList == null || workList.length < 1) {
      req = null;
    }
    else {
      req = workList[idx];
    }
    feedback.receivedFragmentResult(req, workResult, idx);
    IGPProgram best = workResult.getFittest();
    if (best == null) {
      best = workResult.getPopulation().determineFittestProgram();
    }
    resultReceived(best);
    return workResult;
  }

  /**
   * If necessary: override to implement your evolution cycle individually.
   *
   * @param a_gcmed the GridClient mediator
   * @param a_receiveOnly false: Don't send any work requests, just receive
   * results from former evolutions
   *
   * @throws Exception
   */
  protected void evolve(IGridClientMediator a_gcmed, boolean a_receiveOnly)
      throws Exception {
    // Do the complete evolution cycle until end.
    // ------------------------------------------
    IClientFeedbackGP feedback = m_gridConfig.getClientFeedback();
    feedback.beginWork();
    IClientEvolveStrategyGP evolver = m_gridConfig.getClientEvolveStrategy();
    IRequestSplitStrategyGP splitter = m_gridConfig.getRequestSplitStrategy();
    int evolutionIndex = 0;
    do {
      JGAPRequestGP[] workRequests = null;
      boolean deferRequests = false;
      if (!a_receiveOnly) {
        try {
          // Care that not too much work requests are online, do a listing
          // from time to time. If enough requests already there, don't create
          // them any more.
          // -----------------------------------------------------------------
          long lastListing = m_objects.getLastListingRequestsMillis();
          long current = System.currentTimeMillis();
          if (current - lastListing > 60 * 60 * 1) { //60 Seconds * 60 Minutes * 1 Hour
            // Do a listing again after 60 minutes or more.
            // --------------------------------------------
            MessageContext context = new MessageContext(MODULE_CS,
                CONTEXT_WORK_REQUEST, CONTEXT_ID_EMPTY);
            List requests = a_gcmed.listRequests(context, null, null);
            m_objects.setLastListingRequestsMillis(current);
            m_persister.save();
            if (requests != null && requests.size() > 100) {
              deferRequests = true;
              log.info("Deferring creating and sending further requests"
                       + ", maximum reached ("
                       + requests.size() + " found).");
            }
            if (requests != null && requests.size() > 0) {
              // Remove requests from database that are not in list any more.
              // ------------------------------------------------------------
              Map foundKeys = new HashMap();
              Object first = requests.get(0);
              if (String.class.isAssignableFrom(first.getClass())) {
                // Requests of type String can be handled directly.
                // ------------------------------------------------
                for (Object key : requests) {
                  foundKeys.put(key, "");
                }
              }
              else {
                // Requests of type that sub classes have to handle.
                // -------------------------------------------------
                for (Object obj : requests) {
                  Object key = getKeyFromObject(obj);
                  if (key != null) {
                    foundKeys.put(key, "");
                  }
                }
              }
              removeEntries(foundKeys, m_objects.getRequests());
            }
          }
          if (!deferRequests) {
            workRequests = sendWorkRequests(evolutionIndex, evolver, splitter,
                feedback);
          }
          else {
            // Defer creating and sending additional requests.
            // -----------------------------------------------
          }
        } catch (WorkRequestsSendException wex) {
          errorOnSendWorkRequests(wex.getCause(), wex.getWorkRequests());
        }
        if (!deferRequests && !afterSendWorkRequests(workRequests)) {
          break;
        }
      }
      if (this.isInterrupted()) {
        break;
      }
      if (!deferRequests && !a_receiveOnly) {
        evolver.afterWorkRequestsSent();
      }
      if (!m_no_comm) {
        try {
          receiveWorkResults(workRequests);
        } catch (Exception ex) {
          onErrorReceiveWorkResults(workRequests, ex);
        }
      }
      if (!a_receiveOnly && !m_no_evolution) {
        evolver.evolve();
        // Fire listener that one evolution cycle is complete.
        // ---------------------------------------------------
        feedback.completeFrame(evolutionIndex);
        evolutionIndex++;
        // Check if evolution is finished.
        // -------------------------------
        if (evolver.isEvolutionFinished(evolutionIndex)) {
          evolver.onFinished();
          break;
        }
      }
      else {
        a_gcmed.disconnect();
        log.info("Sleeping a while before beginning again...");
        Thread.sleep(40000);
        a_gcmed.connect();
      }
    } while (true);
    try {
      a_gcmed.disconnect();
    } catch (Exception ex) {
      log.error("Disconnecting from server failed!", ex);
    }
    m_gridConfig.getClientFeedback().endWork();
  }

  public void start() {
    try {
      m_gridConfig.validate();
    } catch (Exception ex) {
      throw new RuntimeException(ex);
    }
    super.start();
  }

  public GPConfiguration getConfiguration() {
    return m_gridConfig.getConfiguration();
  }

  public IGridClientMediator getGridClientMediator() {
    return m_gcmed;
  }

  protected IGridConfigurationGP getGridConfigurationGP() {
    return m_gridConfig;
  }

  /**
   * Writes an object to a local file.
   *
   * @param a_obj the object to persist
   * @param a_dir directory to write the file to
   * @param a_filename name of the file to create
   * @throws Exception
   *
   * @author Klaus Meffert
   * @since 3.3.3
   */
  public void writeToFile(Object a_obj, String a_dir, String a_filename)
      throws Exception {
    JGAPGPXStream xstream = new JGAPGPXStream();
    File f = new File(a_dir, a_filename);
//    FileOutputStream fos = new FileOutputStream(f);
    FileWriter fw = new FileWriter(f);
    CompactWriter compact = new CompactWriter(fw);
    xstream.marshal(a_obj, compact);
    fw.close();
  }

  public void setWorkDirectory(String a_workDir)
      throws IOException {
    m_workDir = a_workDir;
    FileKit.createDirectory(m_workDir);
    log.info("Work dir: " + m_workDir);
  }

  public String getWorkDirectory() {
    return m_workDir;
  }

  protected void checkForUpdates(String a_URL, String a_libDir,
                                 String a_workDir)
      throws Exception {
    GridKit.updateModuleLibrary(a_URL, "rjgrid", a_libDir, a_workDir);
  }

  /**
   * Override in sub classes: list available requests
   */
  protected void listRequests() {
  }

  /**
   * Override in sub classes: list available results
   *
   * @author Klaus Meffert
   * @since 3.3.3
   */
  protected void listResults() {
  }

  /**
   * @return false: normal mode, true: do not communicate with the server
   *
   * @author Klaus Meffert
   * @since 3.3.3
   */
  public boolean isNoCommunication() {
    return m_no_comm;
  }

  /**
   * Starts a client (first parameter: name of specific setup class).
   *
   * @param args String[]
   *
   * @author Klaus Meffert
   * @since 3.01
   */
  public static void main(String[] args) {
    try {
      // Setup logging.
      // --------------
      MainCmd.setUpLog4J("client", true);
      // Command line options.
      // ---------------------
      GridNodeClientConfig config = new GridNodeClientConfig();
      Options options = makeOptions();
      CommandLine cmd = MainCmd.parseCommonOptions(options, config, args);
      SystemKit.printHelp(cmd, options);
      String networkMode = cmd.getOptionValue("l", "LAN");
      boolean inWAN;
      if (networkMode != null && networkMode.equals("LAN")) {
        inWAN = false;
      }
      else {
        inWAN = true;
      }
      if (!cmd.hasOption("config")) {
        System.out.println(
            "Please provide a name of the grid configuration class to use");
        System.out.println("An example class would be "
                           +
                           "examples.grid.fitnessDistributed.GridConfiguration");
        System.exit(1);
      }
//      if (args.length < 2) {
//        System.out.println(
//            "Please provide an application name of the grid (textual identifier");
//        System.exit(1);
//      }
      String clientClassName = cmd.getOptionValue("config");
      boolean receiveOnly = cmd.hasOption("receive_only");
      boolean list = cmd.hasOption("list");
      boolean noComm = cmd.hasOption("no_comm");
      boolean noEvolution = cmd.hasOption("no_evolution");
      boolean endless = cmd.hasOption("endless");
      int max_fetch_results = Integer.valueOf(cmd.getOptionValue(
          "max_fetch_results", "0"));
      // Setup and start client.
      // -----------------------
      JGAPClientGP client = new JGAPClientGP(config, clientClassName, inWAN,
          receiveOnly, list, noComm, noEvolution, endless, max_fetch_results);
      // Start the threaded process.
      // ---------------------------
      client.start();
      client.join();
    } catch (Exception ex) {
      ex.printStackTrace();
      System.exit(1);
    }
  }

  protected static Options makeOptions() {
    Options options = new Options();
    options.addOption("l", true, "LAN or WAN");
    options.addOption("no_comm", false,
                      "Don't receive any results, don't send requests");
    options.addOption("no_evolution", false,
                      "Don't perform genetic evolution");
    options.addOption("receive_only", false,
                      "Only receive results, don't send requests");
    options.addOption("endless", false, "Run endlessly");
    options.addOption("config", true, "Grid configuration's class name");
    options.addOption("list", false,
                      "List requests and results");
    options.addOption("max_fetch_results", true,
                      "Maximum number of results to fetch at once");
    options.addOption("help", false,
                      "Display all available options");
    return options;
  }

  protected void removeEntries(Map a_cachedKeys, Map a_foundKeys) {
    Iterator it = a_cachedKeys.keySet().iterator();
    while (it.hasNext()) {
      Object key = it.next();
      if (!a_foundKeys.containsKey(key)) {
        it.remove();
      }
    }
  }

  /**
   * Override in sub classes.
   *
   * @param a_obj the object to get the key from
   * @return the key of the object
   *
   * @throws Exception
   */
  protected String getKeyFromObject(Object a_obj)
      throws Exception {
    return null;
  }

  /**
   * New results has been received. Care that the best of them are stored
   * in case it is a top 3 result.
   *
   * @param a_pop the fittest results received for a work request
   * @return true: new top result
   *
   * @throws Exception
   *
   * @author Klaus Meffert
   * @since 3.3.3
   */
  protected boolean resultReceived(GPPopulation a_pop)
      throws Exception {
    boolean isTopResult = false;
    for (IGPProgram prog : a_pop.getGPPrograms()) {
      if (prog != null) {
        if (resultReceived(prog)) {
          isTopResult = true;
        }
      }
    }
    return isTopResult;
  }

  /**
   * A new result has been received. Care that it is stored to top list on disk
   * in case it is a top 3 result. Also store it in other cases and if the
   * result is not too bad to be able to mix it in when generating new work
   * requests.
   *
   * @param a_fittest the fittest result received for a work request
   *
   * @return true: new top result
   *
   * @throws Exception
   *
   * @author Klaus Meffert
   * @since 3.3.3
   */
  protected boolean resultReceived(IGPProgram a_fittest)
      throws Exception {
    if (a_fittest == null) {
      return false;
    }
    /**@todo jeden Worker einer von n (rein logischen) Gruppen zuteilen.
     * Pro logischer Gruppe top n Ergebnisse halten
     */
    try {
      Map topAll = m_objects.getTopResults();
      String appid = m_gridConfig.getContext().getAppId();
      List topApp = topAll.get(appid);
      if (topApp == null) {
        topApp = new Vector();
        topAll.put(appid, topApp);
      }
      int fitter = 0;
      Iterator it = topApp.iterator();
      Object worstEntry = null;
      double worstFitness = -1;
      String norm = a_fittest.toStringNorm(0);
      int count = 0;
      double a_fitness = a_fittest.getFitnessValue();
      if (a_fitness > 12500) {
        // Store online as a backup.
        // -------------------------
        log.info("Backup up good result");
        String title = "fitness_" + NumberKit.niceDecimalNumber(a_fitness, 2);
        m_gcmed.backupResult(a_fittest, "goodResults", title);
      }
      else {
        if (a_fitness > 1750) {
          // Store not too bad result for mixing it in to new work requests.
          // ---------------------------------------------------------------
          log.info("Storing not too bad result for later reusage");
          String title = "ntb_fitness_"
              + DateKit.getNowAsString()
              + "_"
              + NumberKit.niceDecimalNumber(a_fitness, 2)
              + ".jgap";
          // Store in separate subdir.
          // -------------------------
          saveResult(m_ntbResultsDir, title, a_fittest);
        }
      } while (it.hasNext()) {
        IGPProgram prog = (IGPProgram) it.next();
        // Don't allow identical results.
        // ------------------------------
        if (prog.toStringNorm(0).equals(norm)) {
          fitter = 100;
          break;
        }
        double fitness = prog.getFitnessValue();
        if (Math.abs(fitness - a_fittest.getFitnessValue()) < 0.001) {
          fitter = 100;
          break;
        }
        else if (fitness >= a_fitness) {
          fitter++;
        }
        // Determine the worst entry for later replacement.
        // ------------------------------------------------
        if (worstEntry == null ||
            getConfiguration().getGPFitnessEvaluator().
            isFitter(worstFitness, fitness)) {
          worstEntry = prog;
          worstFitness = fitness;
        }
        count++;
      }
      boolean result = true;
      if (fitter < 3 || count > 3) { /**@todo make configurable*/
        // Remove worst result yet and add new fit result.
        // -----------------------------------------------
        if (worstEntry != null && count >= 3) {
          /**@todo compare with toStringNorm(0), use remove(int) instead of remove(Object)*/
          if (!topApp.remove(worstEntry)) {
            log.error("Removing of worst entry failed");
          }
        }
        if (fitter < 3) {
          try {
            GPGenotype.checkErroneousProg(a_fittest, " add top fit", true, false);
          } catch (Throwable t) {
            log.warn("Received program not valid!");
            result = false;
          }
          if (result) {
            a_fitness = a_fittest.getFitnessValue();
            if (a_fitness < 1000) { /**@todo ist nur test!*/
              result = false;
            }
            else {
              topApp.add(a_fittest);
              log.info("Added fit program, fitness: " +
                       NumberKit.niceDecimalNumber(a_fitness, 2));
              log.info("Solution: " + a_fittest.toStringNorm(0));
              result = true;
            }
          }
        }
        else {
          log.info(
              "Result not better than top results received, removed obsolete top result");
          result = false;
        }
      }
      else {
        log.info("Result not better than top results received");
        result = false;
      }
      if (result) {
        /**@todo skip unnecessary data, inject it after reload*/
        //m_persister.save(true, JGAPClientGP.FIELDSTOSKIP);
        m_persister.save();
      }
      return result;
    } catch (Exception ex) {
      ex.printStackTrace();
      throw ex;
    }
  }

  /**
   * Saves a result to disk.
   *
   * @param a_dir the directory to put the result into
   * @param a_filename name of the file to write
   * @param a_obj the result object to write
   *
   * @throws Exception
   *
   * @author Klaus Meffert
   * @since 3.3.4
   */
  protected void saveResult(String a_dir, String a_filename,
                            IGPProgram a_obj)
      throws Exception {
    String filename = FileKit.addFilename(a_dir, a_filename);
    PersistableObject po = new PersistableObject(filename);
    po.setObject(a_obj);
    po.save();
  }

  public String[] getFilenames(String a_dir)
      throws Exception {
    String[] files = FileKit.listFilesInDir(a_dir, null);
    return files;
  }

  /**
   * Presets initial population to be included for input to workers.
   *
   * @param a_workRequest the work request that is about to be sent.
   *
   * @throws Exception
   *
   * @author Klaus Meffert
   * @since 3.3.3
   */
  protected void presetPopulation(JGAPRequestGP a_workRequest)
      throws Exception {
    // Merge previously stored results with new requests.
    // Often preset them as input for worker, sometimes give worker an
    // empty population.
    // -------------------------------------------------------------------
    RandomGenerator randGen = getConfiguration().getRandomGenerator();
    double d = randGen.nextDouble();
    if (d > 0.15d) {
      Map topAll = m_objects.getTopResults();
      String appid = m_gridConfig.getContext().getAppId();
      List topApp = topAll.get(appid);
      int added = 0;
//      int index = 0;
      GPPopulation pop = a_workRequest.getPopulation();
      IGPProgram[] programs = pop.getGPPrograms();
//      while (index < pop.getPopSize() && pop.getGPProgram(index) != null) {
//        index++;
//      }
      List toAdd = new Vector();
      if (topApp != null && topApp.size() > 0) {
        // Merge top results in.
        // ---------------------
        for (IGPProgram prog : topApp) {
          GPGenotype.checkErroneousProg(prog, " before add preset", true);
          toAdd.add(prog);
          added++;
          if (added >= 3 || randGen.nextDouble() > 0.6d) {
            break;
          }
        }
      }
      // Merge not too bad results in.
      // -----------------------------
      String[] results = getFilenames(m_ntbResultsDir);
      if (results != null && results.length > 0) {
        int count = randGen.nextInt(Math.min(5, results.length));
        if (count > 0) {
          if (count > results.length) {
            count = results.length;
          }
          for (int i = 0; i < count; i++) {
            int index = randGen.nextInt(results.length);
            String filename = FileKit.addFilename(m_ntbResultsDir,
                results[index]);
            /**@todo remove results[index]*/
            PersistableObject po = new PersistableObject(filename);
            IGPProgram ntb = (IGPProgram) po.load();
            log.info("Presetting with NTB result");
            added++;
            toAdd.add(ntb);
          }
        }
      }
      // Now merge old and new programs to one pool.
      // -------------------------------------------
      int len = programs.length;
      if (len > 0) {
        len = 0;
        while (len < programs.length && programs[len] != null) {
          len++;
        }
        IGPProgram[] programsNew = (IGPProgram[]) toAdd.toArray(new IGPProgram[] {});
        int size = len + toAdd.size();
        IGPProgram[] allPrograms = new IGPProgram[size];
        if (len > 0) {
          System.arraycopy(programs, 0, allPrograms, 0, len);
        }
        System.arraycopy(programsNew, 0, allPrograms, len, programsNew.length);
        pop.setGPPrograms(allPrograms);
        log.info("Population preset with " + added + " additional programs");
      }
    }
  }

  protected void showCurrentResults()
      throws Exception {
    String appid = m_gridConfig.getContext().getAppId();
    Map topAll = m_objects.getTopResults();
    List topApp = topAll.get(appid);
    if (topApp != null && topApp.size() > 0) {
      log.info("Top evolved results yet:");
      log.info("------------------------");
      boolean changed = false;
      Iterator it = topApp.iterator();
      while (it.hasNext()) {
        IGPProgram prog = it.next();
        try {
          GPGenotype.checkErroneousProg(prog, " as top result", false, true);
        } catch (Throwable t) {
          // Remove invalid program.
          // -----------------------
          it.remove();
          changed = true;
          continue;
        }
        double fitness = prog.getFitnessValue();
        log.info("Fitness " +
                 NumberKit.niceDecimalNumber(fitness, 2));
        if (fitness < 1000) {
          log.info("Removing too bad result with fitness "
                   + NumberKit.niceDecimalNumber(fitness, 2));
          it.remove();
          changed = true;
        }
      }
      if (changed) {
        m_persister.save();
      }
      log.info("");
    }
    else {
      log.info("No top results yet.");
    }
  }

  public int getMaxFetchResults() {
    return m_max_fetch_results;
  }

  private boolean startsWith(String s, String a_prefix) {
    if (s == null || a_prefix == null) {
      return false;
    }
    return s.startsWith(a_prefix);
  }

}

 

Ads help maintain this website.