/*
 * laptool.c
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdlib.h>
#include <stdint.h>
#include <math.h>

#include "pxtools.h"
#include "pxdata.h"
#include "laptool.h"


// Sequence level
static bool definesequenceMode(pxConfig *config);
static bool listsequenceMode(pxConfig *config);

// Run level
static bool definerunMode(pxConfig *config);
static bool pendingrunMode(pxConfig *config);
static bool updaterunMode(pxConfig *config);
// Exposure level
static bool pendingexpMode(pxConfig *config);
static bool exposuresMode(pxConfig *config);
static bool stacksMode(pxConfig *config);
static bool updateexpMode(pxConfig *config);

static bool diffcheckMode(pxConfig *config);

static bool inactiveexpMode(pxConfig *config);

# define MODECASE(caseName, func) \
  case caseName: \
  if (!func(config)) { \
  goto FAIL; \
  } \
  break;

int main(int argc, char **argv)
{
  psLibInit(NULL);

  pxConfig *config = laptoolConfig(NULL, argc, argv);
  if (!config) {
    psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
    goto FAIL;
  }

  switch (config->mode) {
    MODECASE(LAPTOOL_MODE_DEFINESEQUENCE, definesequenceMode);
    MODECASE(LAPTOOL_MODE_LISTSEQUENCE,   listsequenceMode);
    
    MODECASE(LAPTOOL_MODE_DEFINERUN,     definerunMode);
    MODECASE(LAPTOOL_MODE_PENDINGRUN,    pendingrunMode);
    MODECASE(LAPTOOL_MODE_UPDATERUN,     updaterunMode);

    MODECASE(LAPTOOL_MODE_PENDINGEXP,    pendingexpMode);
    MODECASE(LAPTOOL_MODE_EXPOSURES,     exposuresMode);
    MODECASE(LAPTOOL_MODE_STACKS,        stacksMode);
    MODECASE(LAPTOOL_MODE_UPDATEEXP,     updateexpMode);

    MODECASE(LAPTOOL_MODE_DIFFCHECK,     diffcheckMode);
    
    MODECASE(LAPTOOL_MODE_INACTIVEEXP,   inactiveexpMode);
  default:
    psAbort("invalid option (this should not happen)");
  }
  psTrace("laptool", 9, "Attempting to free config\n");
  psFree(config);
  pmConfigDone();
  psLibFinalize();
  exit(EXIT_SUCCESS);

 FAIL:
  psErrorStackPrint(stderr, "\n");
  int exit_status = pxerrorGetExitStatus();

  psFree(config);
  pmConfigDone();
  psLibFinalize();

  exit(exit_status);
}

// Sequence level

static bool definesequenceMode(pxConfig *config)
{
  PS_ASSERT_PTR_NON_NULL(config, false);

  PXOPT_LOOKUP_STR(name,        config->args, "-name",        true, false);
  PXOPT_LOOKUP_STR(description, config->args, "-description", true, false);
  PXOPT_LOOKUP_BOOL(simple,     config->args, "-simple",     false);

  lapSequenceRow *run = lapSequenceRowAlloc(0, // seq_id
					    name,
					    description);
  if (!run) {
    psError(PS_ERR_UNKNOWN, false, "failed to alloc lapSequence object");
    return(true);
  }

  if (!psDBTransaction(config->dbh)) {
    psError(PS_ERR_UNKNOWN, false, "database error");
    return false;
  }

  if (!lapSequenceInsertObject(config->dbh, run)) {
    if (!psDBRollback(config->dbh)) {
      psError(PS_ERR_UNKNOWN, false, "database error");
    }
    psError(PS_ERR_UNKNOWN, false, "database error");
    psFree(run);
    return(true);
  }

  // point of no return
  if (!psDBCommit(config->dbh)) {
    psError(PS_ERR_UNKNOWN, false, "database error");
    return false;
  }

  if (!lapSequencePrintObject(stdout, run, !simple)) {
    psError(PS_ERR_UNKNOWN, false, "failed to print object");
    psFree(run);
    return false;
  }

  psFree(run);

  return true;  
}

static bool listsequenceMode(pxConfig *config)
{
  PS_ASSERT_PTR_NON_NULL(config, false);

  PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
  PXOPT_LOOKUP_U64(limit,   config->args, "-limit",  false, false);
  
  psMetadata *where = psMetadataAlloc();
  PXOPT_COPY_S64(config->args, where, "-seq_id", "seq_id", "==");
  PXOPT_COPY_STR(config->args, where, "-seq_name",   "name",   "LIKE");

  psString query = pxDataGet("laptool_listsequence.sql");
  if (!query) {
    psError(PXTOOLS_ERR_SYS, false, "failed to retrieve SQL statement");
    return false;
  }
  if (psListLength(where->list)) {
    psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
    psStringAppend(&query, " WHERE %s", whereClause);
    psFree(whereClause);
  }

  if (limit) {
    psString limitString = psDBGenerateLimitSQL(limit);
    psStringAppend(&query, " %s", limitString);
    psFree(limitString);
  }
  if (!p_psDBRunQuery(config->dbh, query)) {
    psError(PS_ERR_UNKNOWN, false, "database error");
    psFree(query);
    return false;
  }
  psFree(query);

  psArray *output = p_psDBFetchResult(config->dbh);
  if (!output) {
    psErrorCode err = psErrorCodeLast();
    switch (err) {
    case PS_ERR_DB_CLIENT:
      psError(PXTOOLS_ERR_SYS, false, "database error");
    case PS_ERR_DB_SERVER:
      psError(PXTOOLS_ERR_PROG, false, "database error");
    default:
      psError(PXTOOLS_ERR_PROG, false, "unknown error");
    }

    return false;
  }
  if (!psArrayLength(output)) {
    psTrace("laptool", PS_LOG_INFO, "no rows found");
    psFree(output);
    return true;
  }

  if (psArrayLength(output)) {
    if (!ippdbPrintMetadatas(stdout, output, "lapSequence", !simple)) {
      psError(PS_ERR_UNKNOWN, false, "failed to print array");
      psFree(output);
      return false;
    }
  }

  psFree(output);

  return true;
}
  

// Run level
static bool definerunMode(pxConfig *config)
{
  PS_ASSERT_PTR_NON_NULL(config, false);

  PXOPT_LOOKUP_S64(seq_id,          config->args, "-seq_id",          true, false);
  PXOPT_LOOKUP_STR(projection_cell, config->args, "-projection_cell", true, false);
  PXOPT_LOOKUP_STR(tess_id,         config->args, "-tess_id",         true, false);
  PXOPT_LOOKUP_F64(ra,              config->args, "-ra",              true, false);
  PXOPT_LOOKUP_F64(decl,            config->args, "-decl",            true, false);
  PXOPT_LOOKUP_F32(radius,          config->args, "-radius",          true, false);
  PXOPT_LOOKUP_STR(filter,          config->args, "-filter",          true, false);
  PXOPT_LOOKUP_STR(label,           config->args, "-label",           false, false);
  PXOPT_LOOKUP_STR(dist_group,      config->args, "-dist_group",      false, false);
  PXOPT_LOOKUP_BOOL(all_obsmode,    config->args, "-all_obsmode",     false);
  PXOPT_LOOKUP_BOOL(simple,         config->args, "-simple",          false);

  lapRunRow *run = lapRunRowAlloc(0, // lap_id
				  seq_id,
				  tess_id,
				  projection_cell,
				  filter,
				  "new", // state
				  label,
				  dist_group,
				  NULL, // registered
				  0,    // fault
				  INT64_MAX,    // quick_sass_id
				  INT64_MAX     // final_sass_id
				  );
  if (!run) {
    psError(PS_ERR_UNKNOWN, false, "failed to alloc lapRun object");
    return(true);
  }

  if (!psDBTransaction(config->dbh)) {
    psError(PS_ERR_UNKNOWN, false, "database error");
    return false;
  }

  if (!lapRunInsertObject(config->dbh, run)) {
    if (!psDBRollback(config->dbh)) {
      psError(PS_ERR_UNKNOWN, false, "database error");
    }
    psError(PS_ERR_UNKNOWN, false, "database error");
    psFree(run);
    return(true);
  }


  if (!lapRunPrintObject(stdout, run, !simple)) {
    if (!psDBRollback(config->dbh)) {
      psError(PS_ERR_UNKNOWN, false, "database error");
    }
    psError(PS_ERR_UNKNOWN, false, "failed to print object");
    psFree(run);
    return false;
  }

  psS64 lap_id = psDBLastInsertID(config->dbh);

  // Find the input exposures
  psString query = pxDataGet("laptool_definerun.sql");
  if (!query) {
    if (!psDBRollback(config->dbh)) {
      psError(PS_ERR_UNKNOWN, false, "database error");
    }

    psError(PXTOOLS_ERR_SYS, false, "failed to retrieve SQL statement");
    return(false);
  }

  // Add constraints to the exposure search:
  psMetadata *where = psMetadataAlloc();
  PXOPT_COPY_STR(config->args, where, "-filter", "rawExp.filter", "==");

  psMetadata *chipWhere = psMetadataAlloc();
  PXOPT_COPY_S64(config->args, chipWhere, "-seq_id", "lapRun.seq_id", "==");
  
  // This seems unnecessarily clunky.
  if (!all_obsmode) {
    //    fprintf(stderr, "Not doing all obsmodes!\n");
    if (!psMetadataLookupStr(NULL,config->args,"-obsmode")) {
      //      fprintf(stderr, "Not doing all obsmodes and none specified!\n");
      psMetadataAddStr(where,PS_LIST_TAIL,"rawExp.obs_mode",0,"==","3PI");
    }
    pxAddLabelSearchArgs (config, where, "-obsmode", "rawExp.obs_mode", "==");
  }
  
  psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
  if (!pxspaceAddWhere(config, &whereClause, "rawExp")) {
    if (!psDBRollback(config->dbh)) {
      psError(PS_ERR_UNKNOWN, false, "database error");
    }

    psError(psErrorCodeLast(), false, "pxSpaceAddWhere failed");
    return(false);
  }

  psString chipWhereClause = psDBGenerateWhereConditionSQL(chipWhere,NULL);
  if (whereClause) {
    psStringSubstitute(&query,whereClause,"@WHERE@");
  }
  if (chipWhereClause) {
    psStringSubstitute(&query,chipWhereClause,"@CHIPWHERE@");
  }
  psFree(where);
  psFree(chipWhere);

  // Fetch exposures
  if (!p_psDBRunQuery(config->dbh, query)) {
    if (!psDBRollback(config->dbh)) {
      psError(PS_ERR_UNKNOWN, false, "database error");
    }

    psError(PS_ERR_UNKNOWN, false, "database error");
    psFree(query);
    return(false);
  }
  psFree(query);

  psArray *output = p_psDBFetchResult(config->dbh);
  if (!output) {
    if (!psDBRollback(config->dbh)) {
      psError(PS_ERR_UNKNOWN, false, "database error");
    }

    psError(PS_ERR_UNKNOWN, false, "database error");
    return(false);
  }
  if (!psArrayLength(output)) {
    if (!psDBRollback(config->dbh)) {
      psError(PS_ERR_UNKNOWN, false, "database error");
    }

    psTrace("laptool", PS_LOG_INFO, "no rows found");
    psFree(output);
    return(true);
  }

  // Insert the exposure data
  for (long i = 0; i < output->n; i++) {
    psMetadata *row = output->data[i]; // Row from select
    // Add default values from this run:
    psMetadataAddS64(row, PS_LIST_TAIL, "lap_id", 0, "", lap_id);
    psMetadataAddS64(row, PS_LIST_TAIL, "pair_id", 0, "", INT64_MAX);
    psMetadataAddStr(row, PS_LIST_TAIL, "data_state", 0, "", "new");
    lapExpRow *lapExp = lapExpObjectFromMetadata(row);
    lapExp->lap_id = lap_id;

    if (!lapExpInsertObject(config->dbh,lapExp)) {
      if (!lapExpPrintObject(stdout, lapExp, !simple)) {
	if (!psDBRollback(config->dbh)) {
	  psError(PS_ERR_UNKNOWN, false, "database error");
	}
	psError(PS_ERR_UNKNOWN, false, "failed to print object");
	psFree(run);
	return false;
      }
      
      if (!psDBRollback(config->dbh)) {
	psError(PS_ERR_UNKNOWN, false, "database error");
      }
      psError(PS_ERR_UNKNOWN, false, "database error");
      psFree(output);
      psFree(lapExp);
      return(false);
    }
  }

  // point of no return
  if (!psDBCommit(config->dbh)) {
    psError(PS_ERR_UNKNOWN, false, "database error");
    return false;
  }

  
  psFree(output);
  return(true);  
}

static bool pendingrunMode(pxConfig *config)
{
  PS_ASSERT_PTR_NON_NULL(config, false);

  PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
  
  psMetadata *where = psMetadataAlloc();
  PXOPT_COPY_S64(config->args, where, "-seq_id", "seq_id", "==");
  PXOPT_COPY_S64(config->args, where, "-lap_id", "lap_id", "==");
  PXOPT_COPY_STR(config->args, where, "-projection_cell", "projection_cell", "==");
  PXOPT_COPY_STR(config->args, where, "-filter", "filter", "==");
  //  PXOPT_COPY_STR(config->args, where, "-label", "label", "==");
  PXOPT_COPY_STR(config->args, where, "-state", "state", "==");
  PXOPT_COPY_STR(config->args, where, "-fault", "fault", "==");

  pxAddLabelSearchArgs(config, where, "-label", "lapRun.label", "==");
  
  psString query = pxDataGet("laptool_pendingrun.sql");
  if (!query) {
    psError(PXTOOLS_ERR_SYS, false, "failed to retrieve SQL statement");
    return false;
  }
  if (psListLength(where->list)) {
    psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
    psStringAppend(&query, " WHERE %s", whereClause);
    psFree(whereClause);
  }
  
  if (!p_psDBRunQuery(config->dbh, query)) {
    psError(PS_ERR_UNKNOWN, false, "database error");
    psFree(query);
    return false;
  }
  psFree(query);

  psArray *output = p_psDBFetchResult(config->dbh);
  if (!output) {
    psErrorCode err = psErrorCodeLast();
    switch (err) {
    case PS_ERR_DB_CLIENT:
      psError(PXTOOLS_ERR_SYS, false, "database error");
    case PS_ERR_DB_SERVER:
      psError(PXTOOLS_ERR_PROG, false, "database error");
    default:
      psError(PXTOOLS_ERR_PROG, false, "unknown error");
    }

    return false;
  }
  if (!psArrayLength(output)) {
    psTrace("laptool", PS_LOG_INFO, "no rows found");
    psFree(output);
    return true;
  }

  if (psArrayLength(output)) {
    if (!ippdbPrintMetadatas(stdout, output, "lapRun", !simple)) {
      psError(PS_ERR_UNKNOWN, false, "failed to print array");
      psFree(output);
      return false;
    }
  }

  psFree(output);

  return true;
}

static bool updaterunMode(pxConfig *config)
{
  PS_ASSERT_PTR_NON_NULL(config, false);

  psMetadata *where = psMetadataAlloc();
  PXOPT_LOOKUP_S64(lap_id, config->args, "-lap_id", true, false);
  PXOPT_COPY_S64(config->args, where, "-lap_id", "lap_id", "==");

  psMetadata *values = psMetadataAlloc();
  PXOPT_LOOKUP_STR(state, config->args, "-set_state", false, false);
  PXOPT_COPY_STR(config->args, values, "-set_state", "state", "==");
  PXOPT_COPY_S16(config->args, values, "-fault",     "fault", "==");
  PXOPT_COPY_STR(config->args, values, "-set_label", "label", "==");
  PXOPT_COPY_S64(config->args, values, "-set_quick_sass_id", "quick_sass_id", "==");
  PXOPT_COPY_S64(config->args, values, "-set_final_sass_id", "final_sass_id", "==");

  long rows = psDBUpdateRows(config->dbh, "lapRun", where, values);
  psFree(values);
  
  if (rows) {
    // We're done with these exposures now, so mark them as inactive.
    if (state) {
      if ((strcmp(state,"drop") == 0)||
	  (strcmp(state,"full") == 0)) {
	values = psMetadataAlloc();
	psMetadataAddBool(values, PS_LIST_TAIL, "active", 0, "", false);
	long exps = psDBUpdateRows(config->dbh, "lapExp", where, values);
	
	if (exps) {
	  return(true);
	}
	else {
	  return(true); // We shouldn't really fail if we didn't change anything. Maybe there's nothing to change.
	}
      }
    }
  }

  return(true);
}

// Exposure level

static bool pendingexpMode(pxConfig *config)
{
  PS_ASSERT_PTR_NON_NULL(config, false);

  PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
  PXOPT_LOOKUP_U64(limit,   config->args, "-limit",  false, false);
  
  psMetadata *where = psMetadataAlloc();
  PXOPT_COPY_S64(config->args, where, "-lap_id", "lap_id", "==");

  psString query = pxDataGet("laptool_pendingexp.sql");
  if (!query) {
    psError(PXTOOLS_ERR_SYS, false, "failed to retrieve SQL statement");
    return false;
  }
  if (psListLength(where->list)) {
    psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
    psStringAppend(&query, "\n AND %s", whereClause);
    psFree(whereClause);
  }
  psStringAppend(&query, " ORDER BY rawExp.dateobs ");
  
  if (limit) {
    psString limitString = psDBGenerateLimitSQL(limit);
    psStringAppend(&query, "\n %s", limitString);
    psFree(limitString);
  }

  
  if (!p_psDBRunQuery(config->dbh, query)) {
    psError(PS_ERR_UNKNOWN, false, "database error");
    psFree(query);
    return false;
  }
  psFree(query);

  psArray *output = p_psDBFetchResult(config->dbh);
  if (!output) {
    psErrorCode err = psErrorCodeLast();
    switch (err) {
    case PS_ERR_DB_CLIENT:
      psError(PXTOOLS_ERR_SYS, false, "database error");
    case PS_ERR_DB_SERVER:
      psError(PXTOOLS_ERR_PROG, false, "database error");
    default:
      psError(PXTOOLS_ERR_PROG, false, "unknown error");
    }

    return false;
  }
  if (!psArrayLength(output)) {
    psTrace("laptool", PS_LOG_INFO, "no rows found");
    psFree(output);
    return true;
  }

  if (psArrayLength(output)) {
    if (!ippdbPrintMetadatas(stdout, output, "lapExp", !simple)) {
      psError(PS_ERR_UNKNOWN, false, "failed to print array");
      psFree(output);
      return false;
    }
  }

  psFree(output);

  return true;
}


static bool exposuresMode(pxConfig *config)
{
  PS_ASSERT_PTR_NON_NULL(config, false);
  PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
  PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
  
  psMetadata *where = psMetadataAlloc();
  PXOPT_COPY_S64(config->args, where, "-lap_id", "lap_id", "==");
  PXOPT_COPY_S64(config->args, where, "-exp_id", "lapExp.exp_id", "==");
  
  psString query = pxDataGet("laptool_exposures.sql");
  if (!query) {
    psError(PXTOOLS_ERR_SYS, false, "failed to retrieve SQL statement");
    return(false);
  }
  psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
  if (whereClause) {
    psStringSubstitute(&query,whereClause,"@WHERE@");
  }
  
  psString limitString = NULL;
  if (limit) {
    limitString = psDBGenerateLimitSQL(limit);
    psStringPrepend(&limitString, "\n");
  }

  if (!p_psDBRunQueryF(config->dbh, query, whereClause, limitString ? limitString : "")) {
    psError(PXTOOLS_ERR_PROG, false, "database error");
    psFree(limitString);
    psFree(query);
    psFree(whereClause);
    return(false);
  }
  psFree(limitString);
  psFree(query);
  psFree(whereClause);
  
  psArray *output = p_psDBFetchResult(config->dbh);
  if (!output) {
    psError(PS_ERR_UNKNOWN, false, "database error");
    return(false);
  }
  if (!psArrayLength(output)) {
    psTrace("laptool", PS_LOG_INFO, "no rows found");
    psFree(output);
    return(true);
  }
  
  if (!ippdbPrintMetadatas(stdout, output, "lapExp", !simple)) {
    psError(PS_ERR_UNKNOWN, false, "failed to print array");
    psFree(output);
    return(false);
  }

  psFree(output);
  return(true);
}

static bool stacksMode(pxConfig *config)
{
  PS_ASSERT_PTR_NON_NULL(config, false);
  PXOPT_LOOKUP_S64(lap_id, config->args, "-lap_id", true, false);

  PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
  PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);

  psMetadata *where = psMetadataAlloc();
  PXOPT_COPY_S64(config->args, where, "-lap_id", "lapRun.lap_id", "==");
  
  psString query = pxDataGet("laptool_stacks.sql");
  if (!query) {
    psError(PXTOOLS_ERR_SYS, false, "failed to retrieve SQL statement");
    return(false);
  }
  
  if (psListLength(where->list)) {
    psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
    psStringPrepend(&whereClause, "\n AND ");
    psStringSubstitute(&query,whereClause,"@WHERE@");
    psFree(whereClause);
  }

  if (limit) {
    psString limitString = psDBGenerateLimitSQL(limit);
    psStringAppend(&query, " %s", limitString);
    psFree(limitString);
  }


  if (!p_psDBRunQuery(config->dbh, query)) {
    psError(PS_ERR_UNKNOWN, false, "database error");
    psFree(query);
    return false;
  }
  psFree(query);

  psArray *output = p_psDBFetchResult(config->dbh);
  if (!output) {
    psErrorCode err = psErrorCodeLast();
    switch (err) {
    case PS_ERR_DB_CLIENT:
      psError(PXTOOLS_ERR_SYS, false, "database error");
    case PS_ERR_DB_SERVER:
      psError(PXTOOLS_ERR_PROG, false, "database error");
    default:
      psError(PXTOOLS_ERR_PROG, false, "unknown error %d",err);
    }

    return false;
  }
  if (!psArrayLength(output)) {
    psTrace("laptool", PS_LOG_INFO, "no rows found");
    psFree(output);
    return true;
  }

  if (psArrayLength(output)) {
    if (!ippdbPrintMetadatas(stdout, output, "lapRunStacks", !simple)) {
      psError(PS_ERR_UNKNOWN, false, "failed to print array");
      psFree(output);
      return false;
    }
  }

  psFree(output);
  return(true);
}

static bool updateexpMode(pxConfig *config)
{
  PS_ASSERT_PTR_NON_NULL(config, false);

  psMetadata *where = psMetadataAlloc();
  PXOPT_LOOKUP_S64(lap_id,  config->args, "-lap_id",  true, false);
  PXOPT_LOOKUP_S64(exp_id,  config->args, "-exp_id",  true, false);
  PXOPT_LOOKUP_S64(chip_id, config->args, "-chip_id", false, false);

  PXOPT_LOOKUP_S64(set_chip_id, config->args, "-set_chip_id", false, false);
  PXOPT_LOOKUP_S64(set_pair_id, config->args, "-set_pair_id", false, false);
  PXOPT_LOOKUP_STR(set_data_state, config->args, "-set_data_state", false, false);
  PXOPT_LOOKUP_BOOL(private,    config->args, "-private",     false);
  PXOPT_LOOKUP_BOOL(public,     config->args, "-public",      false);
  PXOPT_LOOKUP_BOOL(pairwise,   config->args, "-pairwise",    false);
  PXOPT_LOOKUP_BOOL(nopairwise, config->args, "-nopairwise",  false);
  PXOPT_LOOKUP_BOOL(active,     config->args, "-active",      false);
  PXOPT_LOOKUP_BOOL(inactive,   config->args, "-inactive",    false);


  if (private && public) {
    psError(PS_ERR_UNKNOWN, false, "only one of -private and -public may be selected");
  }
  if (active && inactive) {
    psError(PS_ERR_UNKNOWN, false, "only one of -active and -inactive may be selected");
  }
  if (pairwise && nopairwise) {
    psError(PS_ERR_UNKNOWN, false, "only one of -pairwise and -nopairwise may be selected");
  }

  PXOPT_COPY_S64(config->args, where, "-lap_id", "lap_id", "==");
  PXOPT_COPY_S64(config->args, where, "-exp_id", "exp_id", "==");
  PXOPT_COPY_S64(config->args, where, "-chip_id", "chip_id", "==");


  psMetadata *values = psMetadataAlloc();
  if (set_chip_id) {
    PXOPT_COPY_S64(config->args, values, "-set_chip_id", "chip_id", "==");
  }
  if (set_pair_id) {
    PXOPT_COPY_S64(config->args, values, "-set_pair_id", "pair_id", "==");
  }
  if (private) {
    psMetadataAddBool(values, PS_LIST_TAIL, "private", 0, "==", true);
  }
  else if (public) {
    psMetadataAddBool(values, PS_LIST_TAIL, "private", 0, "==", false);
  }
  if (active) {
    psMetadataAddBool(values, PS_LIST_TAIL, "active", 0, "==", true);
  }
  else if (inactive) {
    psMetadataAddBool(values, PS_LIST_TAIL, "active", 0, "==", false);
  }
  if (pairwise) {
    psMetadataAddBool(values, PS_LIST_TAIL, "pairwise", 0, "==", true);
  }
  else if (nopairwise) {
    psMetadataAddBool(values, PS_LIST_TAIL, "pairwise", 0, "==", false);
  }
  if (set_data_state) {
    PXOPT_COPY_STR(config->args, values, "-set_data_state", "data_state", "==");
  }
  long rows = psDBUpdateRows(config->dbh,"lapExp",where,values);
  if (rows) {
    return(true);
  }
/*   else { */
/*     return(false); */
/*   }   */
  return(true);
}

static bool diffcheckMode(pxConfig *config)
{
  PS_ASSERT_PTR_NON_NULL(config, false);
  PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
  
  psMetadata *where = psMetadataAlloc();
  PXOPT_COPY_S64(config->args, where, "-lap_id", "lapRun.lap_id", "==");
  PXOPT_COPY_S64(config->args, where, "-exp_id",  "exp_id", "==");
  PXOPT_COPY_S64(config->args, where, "-chip_id", "chip_id", "==");
  PXOPT_COPY_S64(config->args, where, "-warp_id", "warp_id", "==");
  PXOPT_COPY_S64(config->args, where, "-seq_id",  "seq_id", "==");
  PXOPT_COPY_STR(config->args, where, "-skycell_id", "skycell_id", "==");

  psString query = pxDataGet("laptool_WSdiff_check.sql");
  if (!query) {
    psError(PXTOOLS_ERR_SYS, false, "failed to retrieve SQL statement");
    return(false);
  }
  
  if (psListLength(where->list)) {
    psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
    psStringPrepend(&whereClause, "\n AND ");
    psStringSubstitute(&query,whereClause,"@WHERE@");
    psFree(whereClause);
  }

  if (!p_psDBRunQuery(config->dbh, query)) {
    psError(PS_ERR_UNKNOWN, false, "database error");
    psFree(query);
    return false;
  }
  psFree(query);

  psArray *output = p_psDBFetchResult(config->dbh);
  if (!output) {
    psErrorCode err = psErrorCodeLast();
    switch (err) {
    case PS_ERR_DB_CLIENT:
      psError(PXTOOLS_ERR_SYS, false, "database error");
    case PS_ERR_DB_SERVER:
      psError(PXTOOLS_ERR_PROG, false, "database error");
    default:
      psError(PXTOOLS_ERR_PROG, false, "unknown error %d",err);
    }

    return false;
  }
  if (!psArrayLength(output)) {
    psTrace("laptool", PS_LOG_INFO, "no rows found");
    psFree(output);
    return true;
  }

  if (psArrayLength(output)) {
    if (!ippdbPrintMetadatas(stdout, output, "lapRunWarpStackSkycells", !simple)) {
      psError(PS_ERR_UNKNOWN, false, "failed to print array");
      psFree(output);
      return false;
    }
  }

  psFree(output);
  return(true);
}    

static bool inactiveexpMode(pxConfig *config)
{
  PS_ASSERT_PTR_NON_NULL(config, false);
  PXOPT_LOOKUP_S64(lap_id,          config->args, "-lap_id",          true, false);
  
  psMetadata *where = psMetadataAlloc();
  PXOPT_COPY_S64(config->args, where, "-lap_id", "lap_id", "==");
  
  PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
  PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
  
  psString query = pxDataGet("laptool_inactiveexp.sql");
  if (!query) {
    psError(PXTOOLS_ERR_SYS, false, "failed to retrieve SQL statement");
    return(false);
  }
  psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
  if (whereClause) {
    psStringSubstitute(&query,whereClause,"@WHERE@");
  }
  
  psString limitString = NULL;
  if (limit) {
    limitString = psDBGenerateLimitSQL(limit);
    psStringPrepend(&limitString, "\n");
  }

  if (!p_psDBRunQueryF(config->dbh, query, whereClause, limitString ? limitString : "")) {
    psError(PXTOOLS_ERR_PROG, false, "database error");
    psFree(limitString);
    psFree(query);
    psFree(whereClause);
    return(false);
  }
  psFree(limitString);
  psFree(query);
  psFree(whereClause);
  
  psArray *output = p_psDBFetchResult(config->dbh);
  if (!output) {
    psError(PS_ERR_UNKNOWN, false, "database error");
    return(false);
  }
  if (!psArrayLength(output)) {
    psTrace("laptool", PS_LOG_INFO, "no rows found");
    psFree(output);
    return(true);
  }
  
  if (!ippdbPrintMetadatas(stdout, output, "lapExp", !simple)) {
    psError(PS_ERR_UNKNOWN, false, "failed to print array");
    psFree(output);
    return(false);
  }

  psFree(output);
  return(true);
}

