import config, image, vector, tiff, draw

class HoughCircle:
    int x, y
    int radius
    int pixels
    int accum
    int max # value the accumulator would have if all pixels of the circle
    # existed at maximum intensity

static int **def lookup_table(int min_r, max_r):
    int nr = 1 + max_r - min_r
    int **lut = ALLOCATE(nr * sizeof *lut)
    for int r = min_r; r <= max_r; r++:
        int ir = r - min_r
        # we use pixel resolution for the circumference of the circle
        int na = 2 * pi * r
        lut[ir] = ALLOCATE(1 + na * 2 * sizeof *lut[ir])
        lut[ir][0] = na
        double a = 0, da = 2 * pi / na
        for int ia = 0; ia < na; ia++:
            int rcos = round(r * cos(a))
            int rsin = round(r * sin(a))
            lut[ir][1 + ia * 2 + 0] = rcos
            lut[ir][1 + ia * 2 + 1] = rsin
            a += da
    return lut

Vector *def hough_circle(Image *image,
    int min_r, max_r, min_cx, max_cx, min_cy, max_cy,
    int threshold, hint_r, hint_x, hint_y,
    char const *what, char const *outputdir, char const *outputwhat):
    """
    We use a three dimensional parameter space a, b, r. Pixels on a circle are
    given by:
    x = a + r * cos(theta)
    y = b + r * sin(theta)
    theta [0..360)
    That is, a and b are the center ofthe circle, and r is the radius.

    Now, if we find that a point x/y in our image lies on a circle, then we
    accumulate each possible a/b/r position in hough space.

    For example, we search for a circle with radius 5. We have a circle pixel at
    0/0. Which possible a/b combinations would this contribute to? In Hough
    space it are all a/b where a circle with center a/b would include the point.
    For example, a circle at 5/0 would include 0/0, as would a circle at 0/5. In
    short, we draw a circle around 0/0 with radius 5 in Hough space.

    The min/max parameters are ranges for the radius and the center position
    relative to the image. The radius as well as the center range may also
    extend to outside the image area.
    """
    int output_accums = 0
    if what and strstr(what, outputwhat): output_accums = 1

    int w = image->w
    int h = image->h
    B *data = image->data.b

    Vector *circles = vector_new(sizeof(HoughCircle))

    int **lut = lookup_table(min_r, max_r)
    int nx = 1 + max_cx - min_cx
    int ny = 1 + max_cy - min_cy
    int *hough = ALLOCATE(nx * ny * sizeof *hough)
    Image *out = None
    if output_accums:
        out = image_new_undef_b(nx, ny)

    for int r = min_r; r <= max_r; r++:
        CLEAR(hough, nx * ny * sizeof *hough)
        int ir = r - min_r
        int *rlut = lut[ir]
        # ok, draw some circles
        for int j = 0; j < h; j++:
            for int i = 0; i < w; i++:
                B c = data[j * w + i]
                if c:
                    int na = rlut[0]
                    for int ia = 0; ia < na; ia++:
                        int rcos = rlut[1 + 2 * ia + 0]
                        int rsin = rlut[1 + 2 * ia + 1]
                        int a = i - rcos
                        int b = j - rsin
                        if a >= min_cx and a <= max_cx and\
                            b >= min_cy and b <= max_cy:
                            hough[a - min_cx + nx * (b - min_cy)] += c

        # Since we draw non-antialiased and rather crude circles, pixels likely
        # can be off by a pixel or so. Therefore we collect neighbor
        # pixels for each pixel as well.
        for int j = 1; j < ny - 1; j++:
            for int i = 3; i < nx - 3; i++:
                int sum = 0
                for int k = -1; k <= 1; k++:
                    for int l = -3; l <= 3; l++:
                        sum += hough[(i + l) + (j + k) * nx]
                        
                hough[i + j * nx] = sum / 21

        # If we have a hint circle, anything outside is discarded.
        if hint_r:
            int rr = hint_r * hint_r
            for int j = 0; j < ny; j++:
                for int i = 0; i < nx; i++:
                    int dx = min_cx + i - hint_x
                    int dy = min_cy + j - hint_y
                    if dx * dx + dy * dy > rr:
                        hough[i + j * nx] = 0
                    

        # find maximum accumulator value
        int max = 1
        for int j = 0; j < ny; j++:
            for int i = 0; i < nx; i++:
                if hough[i + j * nx] > max:
                    max = hough[i + j * nx]

        # TODO: As we actually will get slight ellipses most of the time, instead
        # of non-maximum suppression, some kind of averaging would be better.
        #   _ _
        #  / X \
        # | | | |
        #  \_X_/
        #   A B
        #    _
        #   / \
        #  |   |
        #   \_/
        #    C
        #
        # Instead of choosing either one of found circles A and B at the two
        # ends of the ellipse, we want C averaged by them (maybe with the
        # radius slightly compensated).

        # create a grayscale image, with non-maxima suppressed and faint circles
        # thresholded
        for int j = 0; j < ny; j++:
            for int i = 0; i < nx; i++:
                int v = hough[i + j * nx]
                if i < nx - 1 and hough[i + 1 + j * nx] > v: v = 0
                if j > 0 and hough[i + (j - 1) * nx] > v: v = 0
                if i > 0 and hough[i - 1 + j * nx] > v: v = 0
                if j < ny - 1 and hough[i + (j + 1) * nx] > v: v = 0
                int cv = v * 255 / max
                if cv < threshold:
                    v = 0
                    #cv = 0
                if out: out->data.b[i + j * nx] = cv
                if v:
                    HoughCircle c = {min_cx + i, min_cy + j, r,
                        2 * pi * r,
                        v, 2 * pi * r * 255}
                    vector_add(circles, &c)

        if output_accums:
            char name[MAX_PATH]
            sprintf(name, "%s/%s_radius_%d.tiff", outputdir, image->name, r)
            save_tiff_luminance(name, out)

    if out: image_del(out)
    return circles

int def find_best_circle(Vector *circles, int *x, *y, *r, hint, hint_x, hint_y,
    hint_r, hint_weight, debug):
    # FIXME: need to find a better heuristic to get our score..
    int FAIL = -1000
    int best_accum = FAIL
    HoughCircle *best = None

    if not circles->count:
        return 0

    int mindist = 0
    int maxdist = 0
    if hint:
        # Find minimum and maximum distance from hint
        for int i = 0; i < circles->count; i++:
            HoughCircle *c = vector_get(circles, i)
            int dx = c->x - hint_x
            int dy = c->y - hint_y
            int dist =sqrt(dx * dx + dy * dy)
            if mindist == 0 or dist < mindist: mindist = dist
            if dist > maxdist: maxdist = dist
    int middist = (mindist + maxdist) / 2

    int medium_coverage = 0
    for int i = 0; i < circles->count; i++:
        HoughCircle *c = vector_get(circles, i)
        int coverage = c->accum * 1000 / c->max
        medium_coverage += coverage
    medium_coverage /= circles->count

    # Square of radius were distance from hint-circle is unscaled.
    int qdist = 5 * 5

    if debug:
        printf("Selecting best circle..\n")
    for int i = 0; i < circles->count; i++:
        HoughCircle *c = vector_get(circles, i)

        int score
        int dist = 0

        int coverage = c->accum * 1000 / c->max

        if hint:
            # Prefer circles with center closer to the hint position
            int dx = c->x - hint_x
            int dy = c->y - hint_y
            dist = sqrt(dx * dx + dy * dy)
            if dist > middist: score /= 2

            if dist > hint_r:
                if debug:
                    printf(" %d @ %d/%d: %d > %d therefore out\n",
                        c->radius, c->x, c->y, dist, hint_r)
                continue

        # This gives bigger circles an advantages, as they have more pixels in
        # total.
        score = c->accum

        # this means for bigger circles, distance weighs more
        score -= dist * dist * hint_weight * c->radius / qdist
        # this usually has no effect, but for completely identical scores
        # otherwise, we pefer the "outer" outline
        score += c->radius

        # FIXME: this works usually better
        if 0:
            if coverage < medium_coverage: score /= 2
        else:
            score *= coverage
        
        if debug:
            printf(" %d @ %d/%d: %d (%d/%d dis) [%d/%d cov (%d/%d)]\n",
                c->radius, c->x, c->y, score, dist, middist,
                    coverage, medium_coverage, c->accum, c->max)
        if score > best_accum:
            best_accum = score
            best = c
    if debug:
        printf("and the winner is: %d for %d @ %d/%d\n",
            best_accum, best->radius,  best->x, best->y)
    if best_accum == FAIL: return 0
    *x = best->x
    *y = best->y
    *r = best->radius
    return 1