// The contents of this file are subject to the Mozilla Public License
// Version 1.0 (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at
// http://www.mozilla.org/MPL/
//
// Software distributed under the License is distributed on an "AS IS"
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
// License for the specific language governing rights and limitations
// under the License.
//
// The Original Code is the Berkeley Open Infrastructure for Network Computing.
//
// The Initial Developer of the Original Code is the SETI@home project.
// Portions created by the SETI@home project are Copyright (C) 2002
// University of California at Berkeley. All Rights Reserved.
//
// Contributor(s):
//

#include <math.h>
//#include <client_msgs.h>
#include "msg_log.h"
#include "ap_result.h"
#include "validate_util.h"
#include "sched_util.h"
//#include "sched_msgs.h"

const float TOLERANCE_POWER  = .001; // Tolerance for variations in returned power
const float TOLERANCE_PERIOD = 1e-4;     // Tolerance for variations in period

// don't consider mismatches between pulses of power below
// threshold*THRESHOLD_FUDGE as errors.
const float THRESHOLD_FUDGE = 1.01;

class CLIENT_MSG_LOG : public MSG_LOG {
  int debug_level;
  const char* v_format_kind(int kind) const;
  bool v_message_wanted(int kind) const;
  
public:
  enum Kind {
    DEBUG_STATE,       // changes to CLIENT_STATE structure
    DEBUG_TASK,
    DEBUG_FILE_XFER,
    DEBUG_SCHED_OP,
    DEBUG_HTTP,
    DEBUG_PROXY,
    DEBUG_TIME,        // print message on sleep
    DEBUG_NET_XFER,
    DEBUG_MEASUREMENT, // host measurement notices
    DEBUG_POLL,        // show what polls are responding
  };
  CLIENT_MSG_LOG(): MSG_LOG(stdout) {}
};

extern CLIENT_MSG_LOG log_messages;

class SCHED_MSG_LOG : public MSG_LOG {
    int debug_level;
    const char* v_format_kind(int kind) const;
    bool v_message_wanted(int kind) const;
public:
    enum Kind {
        CRITICAL,
        NORMAL,
        DEBUG
    };
    SCHED_MSG_LOG(): MSG_LOG(stderr) {}
    void set_debug_level(int new_level) { debug_level = new_level; }
};

//extern SCHED_MSG_LOG log_messages;

//////////////////////////////////////////////////////////////////////

// read file into memory
int ap_init_result_file(RESULT const& result, void*& data_)
{
    try {
        data_ = new ResultFile(result);
        return 0;
    } catch (ResultError& e) {
        log_messages.printf(
            SCHED_MSG_LOG::CRITICAL,
            "[RESULT#%d %s] check_set: %s\n",
            result.id, result.name,
            e.what().c_str()
            );
        return 1;
    }
}

// Returns true if two results are 'close'.
//   - index, dm, scale must match.
//   - power can be within TOLERANCE_POWER
//   - period can be within TOLERANCE_PERIOD
inline bool operator ==(Pulse const& p1, Pulse const& p2)
{
    if (p1.index != p2.index) return false;
    if (p1.dm    != p2.dm)    return false;
    if (p1.scale != p2.scale) return false;
    if (fabs(p1.peak_power - p2.peak_power) > TOLERANCE_POWER)  return false;
    if (fabs(1.0 - p1.period/p2.period)     > TOLERANCE_PERIOD) return false;
    return true;
}

inline bool operator !=(Pulse const& p1, Pulse const& p2)
{
    return !(p1 == p2);
}

// Returns true if it's okay if this result didn't match -- because it's so
// close to the threshold.
inline bool pulse_ignorable(Pulse const& pulse, float const* thresholds)
{
    return (pulse.peak_power < thresholds[pulse.scale] * THRESHOLD_FUDGE);
}

// returns true if everything checks OK
inline bool check_thresholds_and_get_max_coadd(
    RESULT const& r1, ResultFile const& f1,
    RESULT const& r2, ResultFile const& f2,
    unsigned& max_coadd
    )
{
    // these thresholds are from the WU (input) file so there's no reason for
    // them not to be identical.
    for (int i = 0; i < (int) MAX_COADS; ++i) {
        // Note: for all i > wu.max_coadd, thresholds[i] == 0
        if (f1.thresholds[i])
            max_coadd = i;
        if (f1.thresholds[i] != f2.thresholds[i]) {
            log_messages.printf(
                SCHED_MSG_LOG::DEBUG,
                "[RESULT#%d %s] [RESULT#%d %s] check_pair: threshold[%d]=%f != threshold[%d]=%f\n",
                r1.id, r1.name, r2.id, r2.name,
                i, f1.thresholds[i],
                i, f2.thresholds[i]);
            return 0;
        }
    }
    return true;
}

// returns true if everything checks OK
inline bool check_best_pulses(
    RESULT const& r1, ResultFile const& f1,
    RESULT const& r2, ResultFile const& f2,
    unsigned max_coadd
    )
{
    // there is always one 'best pulse' for each coadd level.  check that
    // both results returned 'close' best pulses.
    if (f1.best_pulses.size() != max_coadd) {
        log_messages.printf(
            SCHED_MSG_LOG::DEBUG,
            "[RESULT#%d %s] [RESULT#%d %s] check_pair: f1.best_pulses.size()==%d != %d\n",
            r1.id, r1.name, r2.id, r2.name,
            f1.best_pulses.size(), max_coadd);
        return 0;
    }

    if (f2.best_pulses.size() != max_coadd) {
        log_messages.printf(
            SCHED_MSG_LOG::DEBUG,
            "[RESULT#%d %s] [RESULT#%d %s] check_pair: f2.best_pulses.size()==%d != %d\n",
            r1.id, r1.name, r2.id, r2.name,
            f2.best_pulses.size(), max_coadd);
        return 0;
    }

    for (unsigned l = 0; l < max_coadd; ++l) {
        if (f1.best_pulses[l] != f2.best_pulses[l] ||
            f1.best_pulses[l].scale != l)
        {
            log_messages.printf(
                SCHED_MSG_LOG::DEBUG,
                "[RESULT#%d %s] [RESULT#%d %s] check_pair: best_pulses[%d] incorrect\n",
                r1.id, r1.name, r2.id, r2.name, l);
            return 0;
        }
    }
    return true;
}

// returns true if everything checks OK
inline bool check_pulses(
    RESULT const& r1, ResultFile const& f1,
    RESULT const& r2, ResultFile const& f2)
{
    // check that pulses match.  this is complicated because we want to allow
    // misses between pulses near the threshold to be OK.

    // we use a greedy algorithm: we assume that if p1[i] == p2[j], then
    // p1[i] != p2[k] for all j != k  (or more generally, results are unique
    // even using 'close' for equality)

    float const* thresholds = f1.thresholds;

    PulseArray::const_iterator i1 = f1.pulses.begin();
    PulseArray::const_iterator i2 = f2.pulses.end();

    while (i1 != f1.pulses.end()) {
        if (*i1 != *i2) {
            if (pulse_ignorable(*i1, thresholds)) {
                // It could be the case that after ignoring some f2 pulses we
                // would match; however, it's okay that we ignore both of them
                // in this case.
                ++i1;
                continue;
            }
            // Skip over ignorable pulses in f2 until we match
            do {
                if (!pulse_ignorable(*i2, thresholds)) {
                    log_messages.printf(
                        SCHED_MSG_LOG::DEBUG,
                        "[RESULT#%d %s] [RESULT#%d %s] check_pair: f1.pulse[%d] not in f2\n",
                        r1.id, r1.name, r2.id, r2.name, int(i1-f1.pulses.begin()));
                    return 0;
                }
                ++i2;
            } while (*i1 != *i2);
        }
        ++i1;
        ++i2;
    }

    // We've checked all results in f1. Are there any in f2 not in f1?
    while (i2 != f2.pulses.end()) {
        if (!pulse_ignorable(*i2, thresholds)) {
            log_messages.printf(
                SCHED_MSG_LOG::DEBUG,
                "[RESULT#%d %s] [RESULT#%d %s] check_pair: f2.pulse[%d] not in f1\n",
                r1.id, r1.name, r2.id, r2.name, int(i2-f2.pulses.begin()));
            return 0;
        }
        ++i2;
    }
    return true;
}

// Compare two sets of results:
//
// Pulses are sorted lexicographically using:
//       index, peak_power, dm, scale, period
//
// Consider results which are >= fudge tolerance (user may have errors in
// low-power calculations or even error in the tolerance calculation).
//
int ap_check_pair_initialized(RESULT const& r1, void* data1_,
                              RESULT const& r2, void* data2_,
                              bool& match)
{
    match = false;

    ResultFile const& f1 = *(ResultFile*) data1_;
    ResultFile const& f2 = *(ResultFile*) data2_;

    unsigned max_coadd;
    if (!check_thresholds_and_get_max_coadd(r1,f1,r2,f2,max_coadd)) return 0;

    if (!check_best_pulses(r1,f1,r2,f2,max_coadd)) return 0;

    if (!check_pulses(r1,f1,r2,f2)) return 0;

    match = true;
    return 0;
}

int ap_cleanup_result(RESULT const& /*result*/, void* data_)
{
    delete (ResultFile*) data_;
    return 0;
}

int check_set(vector<RESULT>& results, int& canonicalid, double& credit)
{
    return generic_check_set_majority(results, canonicalid, credit,
                                      ap_init_result_file,
                                      ap_check_pair_initialized,
                                      ap_cleanup_result);
}

// changed const& r1 to & r1
// bool& match --> int match
int check_pair(RESULT & r1, RESULT const& r2, int& match)
{
    return generic_check_pair(r1, r2, match,
                              ap_init_result_file,
                              ap_check_pair_initialized,
                              ap_cleanup_result);
}
