/*
 * Copyright 2012 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef skdiff_DEFINED
#define skdiff_DEFINED

#include "SkBitmap.h"
#include "SkColor.h"
#include "SkColorPriv.h"
#include "SkString.h"
#include "SkTDArray.h"

#if SK_BUILD_FOR_WIN32
    #define PATH_DIV_STR "\\"
    #define PATH_DIV_CHAR '\\'
#else
    #define PATH_DIV_STR "/"
    #define PATH_DIV_CHAR '/'
#endif

#define MAX2(a,b) (((b) < (a)) ? (a) : (b))
#define MAX3(a,b,c) (((b) < (a)) ? MAX2((a), (c)) : MAX2((b), (c)))


struct DiffResource {
    enum Status {
        /** The resource was specified, exists, read, and decoded. */
        kDecoded_Status,
        /** The resource was specified, exists, read, but could not be decoded. */
        kCouldNotDecode_Status,

        /** The resource was specified, exists, and read. */
        kRead_Status,
        /** The resource was specified, exists, but could not be read. */
        kCouldNotRead_Status,

        /** The resource was specified and exists. */
        kExists_Status,
        /** The resource was specified, but does not exist. */
        kDoesNotExist_Status,

        /** The resource was specified. */
        kSpecified_Status,
        /** The resource was not specified. */
        kUnspecified_Status,

        /** Nothing is yet known about the resource. */
        kUnknown_Status,

        /** NOT A VALID VALUE -- used to set up arrays and to represent an unknown value. */
        kStatusCount
    };
    static char const * const StatusNames[DiffResource::kStatusCount];

    /** Returns the Status with this name.
     *  If there is no Status with this name, returns kStatusCount.
     */
    static Status getStatusByName(const char *name);

    /** Returns a text description of the given Status type. */
    static const char *getStatusDescription(Status status);

    /** Returns true if the Status indicates some kind of failure. */
    static bool isStatusFailed(Status status);

    /** Sets statuses[i] if it is implied by selector, unsets it if not.
     *  Selector may be a comma delimited list of status names, "any", or "failed".
     *  Returns true if the selector was entirely understood, false otherwise.
     */
    static bool getMatchingStatuses(char* selector, bool statuses[kStatusCount]);

    DiffResource() : fFilename(), fFullPath(), fBitmap(), fStatus(kUnknown_Status) { };

    /** If isEmpty() indicates no filename available. */
    SkString fFilename;
    /** If isEmpty() indicates no path available. */
    SkString fFullPath;
    /** If empty() indicates the bitmap could not be created. */
    SkBitmap fBitmap;
    Status fStatus;
};

struct DiffRecord {

    // Result of comparison for each pair of files.
    // Listed from "better" to "worse", for sorting of results.
    enum Result {
        kEqualBits_Result,
        kEqualPixels_Result,
        kDifferentPixels_Result,
        kDifferentSizes_Result,
        kCouldNotCompare_Result,
        kUnknown_Result,

        kResultCount  // NOT A VALID VALUE--used to set up arrays. Must be last.
    };
    static char const * const ResultNames[DiffRecord::kResultCount];

    /** Returns the Result with this name.
     *  If there is no Result with this name, returns kResultCount.
     */
    static Result getResultByName(const char *name);

    /** Returns a text description of the given Result type. */
    static const char *getResultDescription(Result result);

    DiffRecord()
        : fBase()
        , fComparison()
        , fDifference()
        , fWhite()
        , fFractionDifference(0)
        , fWeightedFraction(0)
        , fAverageMismatchA(0)
        , fAverageMismatchR(0)
        , fAverageMismatchG(0)
        , fAverageMismatchB(0)
        , fTotalMismatchA(0)
        , fMaxMismatchA(0)
        , fMaxMismatchR(0)
        , fMaxMismatchG(0)
        , fMaxMismatchB(0)
        , fResult(kUnknown_Result) {
    };

    DiffResource fBase;
    DiffResource fComparison;
    DiffResource fDifference;
    DiffResource fWhite;

    /// Arbitrary floating-point metric to be used to sort images from most
    /// to least different from baseline; values of 0 will be omitted from the
    /// summary webpage.
    float fFractionDifference;
    float fWeightedFraction;

    float fAverageMismatchA;
    float fAverageMismatchR;
    float fAverageMismatchG;
    float fAverageMismatchB;

    uint32_t fTotalMismatchA;

    uint32_t fMaxMismatchA;
    uint32_t fMaxMismatchR;
    uint32_t fMaxMismatchG;
    uint32_t fMaxMismatchB;

    /// Which category of diff result.
    Result fResult;
};

typedef SkTDArray<DiffRecord*> RecordArray;

/// A wrapper for any sortProc (comparison routine) which applies a first-order
/// sort beforehand, and a tiebreaker if the sortProc returns 0.
template<typename T> static int compare(const void* untyped_lhs, const void* untyped_rhs) {
    const DiffRecord* lhs = *reinterpret_cast<DiffRecord* const *>(untyped_lhs);
    const DiffRecord* rhs = *reinterpret_cast<DiffRecord* const *>(untyped_rhs);

    // First-order sort... these comparisons should be applied before comparing
    // pixel values, no matter what.
    if (lhs->fResult != rhs->fResult) {
        return (lhs->fResult < rhs->fResult) ? 1 : -1;
    }

    // Passed first-order sort, so call the pixel comparison routine.
    int result = T::comparePixels(lhs, rhs);
    if (result != 0) {
        return result;
    }

    // Tiebreaker... if we got to this point, we don't really care
    // which order they are sorted in, but let's at least be consistent.
    return strcmp(lhs->fBase.fFilename.c_str(), rhs->fBase.fFilename.c_str());
}

/// Comparison routine for qsort; sorts by fFractionDifference
/// from largest to smallest.
class CompareDiffMetrics {
public:
    static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
        if (lhs->fFractionDifference < rhs->fFractionDifference) {
          return 1;
        }
        if (rhs->fFractionDifference < lhs->fFractionDifference) {
          return -1;
        }
        return 0;
    }
};

class CompareDiffWeighted {
public:
    static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
        if (lhs->fWeightedFraction < rhs->fWeightedFraction) {
            return 1;
        }
        if (lhs->fWeightedFraction > rhs->fWeightedFraction) {
            return -1;
        }
        return 0;
    }
};

/// Comparison routine for qsort;  sorts by max(fAverageMismatch{RGB})
/// from largest to smallest.
class CompareDiffMeanMismatches {
public:
    static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
        float leftValue = MAX3(lhs->fAverageMismatchR,
                               lhs->fAverageMismatchG,
                               lhs->fAverageMismatchB);
        float rightValue = MAX3(rhs->fAverageMismatchR,
                                rhs->fAverageMismatchG,
                                rhs->fAverageMismatchB);
        if (leftValue < rightValue) {
            return 1;
        }
        if (rightValue < leftValue) {
            return -1;
        }
        return 0;
    }
};

/// Comparison routine for qsort;  sorts by max(fMaxMismatch{RGB})
/// from largest to smallest.
class CompareDiffMaxMismatches {
public:
    static int comparePixels(const DiffRecord* lhs, const DiffRecord* rhs) {
        uint32_t leftValue = MAX3(lhs->fMaxMismatchR,
                                  lhs->fMaxMismatchG,
                                  lhs->fMaxMismatchB);
        uint32_t rightValue = MAX3(rhs->fMaxMismatchR,
                                   rhs->fMaxMismatchG,
                                   rhs->fMaxMismatchB);
        if (leftValue < rightValue) {
            return 1;
        }
        if (rightValue < leftValue) {
            return -1;
        }

        return CompareDiffMeanMismatches::comparePixels(lhs, rhs);
    }
};


/// Parameterized routine to compute the color of a pixel in a difference image.
typedef SkPMColor (*DiffMetricProc)(SkPMColor, SkPMColor);

// from gm
static inline SkPMColor compute_diff_pmcolor(SkPMColor c0, SkPMColor c1) {
    int dr = SkGetPackedR32(c0) - SkGetPackedR32(c1);
    int dg = SkGetPackedG32(c0) - SkGetPackedG32(c1);
    int db = SkGetPackedB32(c0) - SkGetPackedB32(c1);

    return SkPackARGB32(0xFF, SkAbs32(dr), SkAbs32(dg), SkAbs32(db));
}

/** When finished, dr->fResult should have some value other than kUnknown_Result.
 *  Expects dr->fWhite.fBitmap and dr->fDifference.fBitmap to have the same bounds as
 *  dr->fBase.fBitmap and have a valid pixelref.
 */
void compute_diff(DiffRecord* dr, DiffMetricProc diffFunction, const int colorThreshold);

#endif
