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

#ifndef SkTracker_DEFINED
#define SkTracker_DEFINED

#include <stdio.h>

#include "SkBitmap.h"
#include "SkPoint.h"

// TODO(edisonn): draw plan from point! - list of draw ops of a point, like a tree!
// TODO(edisonn): Minimal PDF to draw some points - remove everything that it is not needed,
//                save pdf uncompressed

#define MAX_TRACKING_POINTS 100

/** \class SkTracker
 *
 *   A Tracker can be attached to a SkTrackDevice and it will store the track pixels.
 *   It can be used with SampleApp to investigate bugs (CL not checked in yet).
 *
 *   The Tracker tracks 2 sets of points
 *     A) one which is expected to issue breackpoints if the pixels are changes
 *     B) one which if changes will disable the breackpoint
 *   For point in A) there are two modes:
 *     A.1) a breackpoint require that any of the points is changed
 *     A.2) a breackpoint require that all of the points is changed
 *   Points in B are allways in "any mode" - chaning the value of any pixel, will disable
 *     the breackpoint
 *
 *   Point in A) are used to show what are the areas of interest, while poit in B are used to
 *     disable breackpoints which would be issued in background change.
 *
 */
class SkTracker {
public:
    SkTracker() : fEnabled(false)
                , fBreakOnAny(false)
                , fCntExpectedTouched(0)
                , fCntExpectedUntouched(0)
                , fHits(0) {}

    virtual ~SkTracker() {}

    // Clears all the points, but preserves the break mode.
    void clearPoints() {
        fCntExpectedTouched = 0;
        fCntExpectedUntouched = 0;
    }

    // Enable the breackpoints.
    void enableTracking(bool b) {
        fEnabled = b;
    }

    // Returns true if breackpoints are enabled.
    bool trackingEnabled() {
        return fEnabled;
    }

    // Puts the tracker in Any mode.
    void any() {
        fBreakOnAny = true;
    }

    // Puts the tracker in Any mode.
    void all() {
        fBreakOnAny = false;
    }

    // returns true in in All mode. False for Any mode.
    bool requireAllExpectedTouched() {
        return !fBreakOnAny;
    }

    // Returns the numbers of points in which if touched, would trigger a breackpoint.
    int cntExpectedTouched() {
        return fCntExpectedTouched;
    }

    // Returns the points which if touched, would trigger a breackpoint.
    // the Tracker owns the array
    const SkIPoint* expectedTouched() {
        return fExpectedTouched;
    }

    // Returns the numbers of points in which if touched, would disable a breackpoint.
    int cntExpectedUntouched() {
        return fCntExpectedUntouched;
    }

    // Returns the points which if touched, would disable a breackpoint.
    // the Tracker owns the array
    const SkIPoint* expectedUntouched() {
        return fExpectedUntouched;
    }

    // Adds a point which if changes in a drawFoo operation, would trigger a breakpoint.
    bool addExpectTouch(int x, int y) {
        if (fCntExpectedTouched >= MAX_TRACKING_POINTS) {
            return false;
        }
        if (found(x, y)) {
            return false;
        }
        fExpectedTouched[fCntExpectedTouched] = SkIPoint::Make(x, y);
        fCntExpectedTouched++;
        return true;
    }

    // Adds a point which if changes in a drawFoo operation, would disable a breakpoint.
    bool addExpectUntouch(int x, int y) {
        if (fCntExpectedUntouched >= MAX_TRACKING_POINTS) {
            return false;
        }
        if (found(x, y)) {
            return false;
        }
        fExpectedUntouched[fCntExpectedUntouched] = SkIPoint::Make(x, y);
        fCntExpectedUntouched++;
        return true;
    }

    // Starts a new rendering session - reset the number of hits.
    void newFrame() {
        fHits = 0;
    }

    // returns the number of breackpoints issues in this rendering session.
    int hits() {
        return fHits;
    }

    // Called before drawFoo to store the state of the pixels
    void before(const SkBitmap& bitmap) {
        if (fCntExpectedTouched == 0) {
            return;
        }

        for (int i = 0 ; i < fCntExpectedTouched; i++) {
            fBeforeTouched[i] = pickColor(bitmap, fExpectedTouched[i].x(), fExpectedTouched[i].y());
        }
        for (int i = 0 ; i < fCntExpectedUntouched; i++) {
            fBeforeUntouched[i] = pickColor(bitmap, fExpectedUntouched[i].x(),
                                            fExpectedUntouched[i].y());
        }
    }

    // Called after drawFoo to evaluate what pixels have changed, it could issue a breakpoint.
    // any/all of the expected touched has to be changed, and all expected untouched must be intact
    void after(const SkBitmap& bitmap) {
        if (fCntExpectedTouched == 0) {
            return;
        }

        bool doBreak;
        if (fBreakOnAny) {
            doBreak = false;
            for (int i = 0 ; i < fCntExpectedTouched; i++) {
                doBreak = doBreak || fBeforeTouched[i] != pickColor(bitmap, fExpectedTouched[i].x(),
                                                                    fExpectedTouched[i].y());
            }
        } else {
            doBreak = true;
            for (int i = 0 ; i < fCntExpectedTouched; i++) {
                doBreak = doBreak && fBeforeTouched[i] != pickColor(bitmap, fExpectedTouched[i].x(),
                                                                    fExpectedTouched[i].y());
            }
        }

        for (int i = 0 ; i < fCntExpectedUntouched; i++) {
            doBreak = doBreak && fBeforeUntouched[i] == pickColor(bitmap, fExpectedUntouched[i].x(),
                                                                  fExpectedUntouched[i].y());
        }

        if (doBreak) {
            fHits++;
            if (fEnabled) {
                breakExecution();
            }
        }
    }

private:
    inline SkColor pickColor(const SkBitmap& bitmap, int x, int y) {
        return bitmap.getColor(x, y);
    }

    void breakExecution() {
        printf("break;\n");
    }

    inline bool found(int x, int y) {
        for (int i = 0 ; i < fCntExpectedTouched; i++) {
            if (x == fExpectedTouched[i].x() && y == fExpectedTouched[i].y()) {
                return true;
            }
        }
        for (int i = 0 ; i < fCntExpectedUntouched; i++) {
            if (x == fExpectedUntouched[i].x() && y == fExpectedUntouched[i].y()) {
                return true;
            }
        }
        return false;
    }


    bool fEnabled;
    // break on any change on expected touched or all.
    bool fBreakOnAny;
    SkIPoint fExpectedTouched[MAX_TRACKING_POINTS];
    SkColor fBeforeTouched[MAX_TRACKING_POINTS];
    int fCntExpectedTouched;

    SkIPoint fExpectedUntouched[MAX_TRACKING_POINTS];
    SkColor fBeforeUntouched[MAX_TRACKING_POINTS];
    int fCntExpectedUntouched;

    int fHits;
};

#endif  // SkTracker_DEFINED