import config, tiff, jpeg

enum ImageType:
    IT_FLOAT
    IT_INT
    IT_BYTE
    IT_RGB

enum ScaleType:
    IST_NEAREST
    IST_BILINEAR
    IST_BICUBIC
#IST_AVERAGE    

class Color:
    B r, g, b

class Image:
    char *path, *name
    int type
    union data:
        V *v
        int *i
        B *b
        Color *rgb
    int w, h
    int row
    int bytes
    int rowbytes
    Image *parent

def image_move(Image *image, int x, y, w, h):
    image->rowbytes = image->parent->rowbytes
    image->row = image->parent->row
    image->data.b = image->parent->data.b + y * image->rowbytes + x * image->bytes
    image->w = w
    image->h = h

Image *def image_window(Image *parent, int x, y, w, h):
    Image *window = image_new(parent->data.b, w, h, parent->type)
    window->parent = parent
    image_move(window, x, y, w, h)
    return window

Image *def image_new(B *data, int w, h, ImageType type):
    Image *i = ALLOCATE0(sizeof *i)
    i->type = type
    int ts = 1
    switch type:
        case IT_FLOAT: ts = sizeof(float); break
        case IT_INT: ts = sizeof(int); break
        case IT_BYTE: ts = 1; break
        case IT_RGB: ts = sizeof(Color); break
    if data: i->data.b = data
    else: i->data.b = ALLOCATE(w * h * ts)
    i->bytes = ts
    i->rowbytes = w * ts
    i->w = i->row = w
    i->h = h
    return i

Image *def image_half_v(Image const *src):
    Image *i = image_new(None, src->w / 2, src->h / 2, src->type)
    for int y = 0; y < i->h; y++:
        for int x = 0; x < i->w; x++:
            V a = src->data.v[(y * 2 + 0) * src->row + (x * 2 + 0)]
            V b = src->data.v[(y * 2 + 0) * src->row + (x * 2 + 1)]
            V c = src->data.v[(y * 2 + 1) * src->row + (x * 2 + 1)]
            V d = src->data.v[(y * 2 + 1) * src->row + (x * 2 + 0)]
            i->data.v[y * i->row + x] = (a + b + c + d) / 4
    return i

Image *def image_copy(Image const *src):
    Image *i = image_new(None, src->w, src->h, src->type)
    # Should work for all types since rowbytes includes the size.
    memcpy(i->data.b, src->data.b, i->rowbytes * i->h)
    if src->name: i->name = strdup(src->name)
    return i

def image_transfer(Image const *src, Image *target,
    int sx, sy, tx, ty, w, h):
    """
    Note: Only works if the target has the same format, for now.
    """

    B *src_ptr = src->data.b + src->rowbytes * sy + src->bytes * sx
    B *target_ptr = target->data.b + target->rowbytes * ty + target->bytes * tx

    for int i = 0; i < h; i++:
        # Should work for all types since rowbytes includes the size.
        memcpy(target_ptr, src_ptr, w * src->bytes)
        src_ptr += src->rowbytes
        target_ptr += target->rowbytes

Image *def image_copy_crop(Image const *src, int x, y, w, h):
    Image *i = image_new(None, w, h, src->type)
    for int row = y; row < y + h; row++:
        if row >= 0 and row < src->h:
            memcpy(i->data.b + i->rowbytes * (row - y),
                src->data.b + src->rowbytes * row + src->bytes * x, i->bytes * w)
        else:
            memset(i->data.b + i->rowbytes * (row - y), 0, i->bytes * w)
    if src->name: i->name = strdup(src->name)
    return i

def image_del(Image *image):
    if not image->parent:
        if image->data.b: FREE(image->data.b)
    if image->name: FREE(image->name)
    if image->path: FREE(image->path)
    FREE(image)

def image_set_path(Image *image, char const *path, int auto_name):
    image->path = strdup(path)
    if auto_name:
        # 0        9    14   19
        # |        |    |    |
        # /tmp/blah/name.jpeg
        #          S    D
        #           a  b
        # a = 10
        # b = 13
        char const *slash = strrchr(path, '/')
        char const *dot = strrchr(path, '.')
        int a, b

        if slash: a = slash + 1 - path
        else: a = 0
        if dot: b = dot - 1 - path
        else: b = strlen(path) - 1

        image->name = malloc(1 + b - a + 1)
        strncpy(image->name, path + a, 1 + b - a)
        image->name[1 + b - a] = '\0'

Image *def image_new_b(B *data, int w, h):
    return image_new(data, w, h, IT_BYTE)

Image *def image_new_undef_b(int w, h):
    return image_new(None, w, h, IT_BYTE)

Image *def image_new_undef_i(int w, h):
    return image_new(None, w, h, IT_INT)

Image *def image_new_undef_f(int w, h):
    return image_new(None, w, h, IT_FLOAT)

Image *def image_new_undef_rgb(int w, h):
    return image_new(None, w, h, IT_RGB)

V def image_mean_area(Image *im, int x, y, w, h):
    V sum = 0
    for int j = y; j < y + h; j++:
        for int i = x; i < x + w; i++:
            sum += im->data.v[j * im->w + i]
    V n = w * h
    return sum / n

V def image_variance_area(Image *im, int x, y, w, h):
    V e = image_mean_area(im, x, y, w, h)
    V sum = 0
    for int j = y; j < y + h; j++:
        for int i = x; i < x + w; i++:
            V v = im->data.v[j * im->w + i]
            v = (v - e) * (v - e)
            sum += v
    V n = w * h
    return sum / n

B def get_near_pixel(Image *image, int x, y):
    if x < 0: x = 0
    if y < 0: y = 0
    if x > image->w - 1: x = image->w - 1
    if y > image->h - 1: y = image->h - 1
    return image->data.b[x + y * image->w]

B def getpixel_bilinear(Image *image, double x, y):
    """
    We compute the pixel value such that it consists of the values of all
    pixels at offset t weighted by:
    (1 - t) if t in [0, 1]
    (1 + t) if t in [-1, 0]
    1       if t = 0
    0       if t < -1 or t > 1

    This means that for any given position, we only need to consider two
    neighbors per dimension or 4 pixels. (Pixels have a grid distance of 1
    already,  so there can only be 4 of them in a square of radius 1.)
    """
    int ix = x
    int iy = y
    double xp = x - ix
    double yp = y - iy
    B c1 = get_near_pixel(image, ix, iy)
    B c2 = get_near_pixel(image, ix + 1, iy)
    B c3 = get_near_pixel(image, ix + 1, iy + 1)
    B c4 = get_near_pixel(image, ix, iy + 1)
    double c = c1 * (1 - xp) * (1 - yp) + c2 * xp * (1 - yp)
    c += c3 * xp * yp + c4 * (1 - xp) * yp
    if c < 0: return 0
    if c > 255: return 255
    return c

B def getpixel_bicubic(Image *image, double x, y):
    """
    We compute the pixel value such that it consists of the values of all
    pixels at offset t weighted by:
    1 - 2 |t|^2 + |t|^3          if |t| <= 1
    4 - 8 |t| + 5 |t|^2 - |t|^3  if 1 < |t| < 2
    0                            if t >= 2
    note:
    1                            if t = 0
    0                            if t = 1

    Since no pixels with offset >= 2 are considered, we only need to look at
    4 neighbors per dimenstion or 16 pixels.
    """
    int ix = x
    int iy = y
    double xp = x - ix
    double yp = y - iy
    B c0 = get_near_pixel(image, ix - 1, iy - 1)
    B c1 = get_near_pixel(image, ix - 0, iy - 1)
    B c2 = get_near_pixel(image, ix + 1, iy - 1)
    B c3 = get_near_pixel(image, ix + 2, iy - 1)
    B c4 = get_near_pixel(image, ix - 1, iy - 0)
    B c5 = get_near_pixel(image, ix - 0, iy - 0)
    B c6 = get_near_pixel(image, ix + 1, iy - 0)
    B c7 = get_near_pixel(image, ix + 2, iy - 0)
    B c8 = get_near_pixel(image, ix - 1, iy + 1)
    B c9 = get_near_pixel(image, ix - 0, iy + 1)
    B ca = get_near_pixel(image, ix + 1, iy + 1)
    B cb = get_near_pixel(image, ix + 2, iy + 1)
    B cc = get_near_pixel(image, ix - 1, iy + 2)
    B cd = get_near_pixel(image, ix - 0, iy + 2)
    B ce = get_near_pixel(image, ix + 1, iy + 2)
    B cf = get_near_pixel(image, ix + 2, iy + 2)

    double s0 = 4 - 8 * (1 + xp) + 5 * pow(1 + xp, 2) - pow(1 + xp, 3)
    double s1 = 1 - 2 * pow(xp, 2) + pow(xp, 3)
    double s2 = 1 - 2 * pow(1 - xp, 2) + pow(fabs(1 - xp), 3)
    double s3 = 4 - 8 * (2 - xp) + 5 * pow(2 - xp, 2) - pow(2 - xp, 3)

    double t0 = 4 - 8 * (1 + yp) + 5 * pow(1 + yp, 2) - pow(1 + yp, 3)
    double t1 = 1 - 2 * pow(yp, 2) + pow(yp, 3)
    double t2 = 1 - 2 * pow(1 - yp, 2) + pow(fabs(1 - yp), 3)
    double t3 = 4 - 8 * (2 - yp) + 5 * pow(2 - yp, 2) - pow(2 - yp, 3)

    double c =\
        c0 * s0 * t0 + c1 * s1 * t0 + c2 * s2 * t0 + c3 * s3 * t0 +\
        c4 * s0 * t1 + c5 * s1 * t1 + c6 * s2 * t1 + c7 * s3 * t1 +\
        c8 * s0 * t2 + c9 * s1 * t2 + ca * s2 * t2 + cb * s3 * t2 +\
        cc * s0 * t3 + cd * s1 * t3 + ce * s2 * t3 + cf * s3 * t3

    if c < 0: return 0
    if c > 255: return 255
    return c

Image *def scale_image(Image *image, int neww, newh, ScaleType how):
    Image *scaled = image_new_undef_b(neww, newh)
    if how == IST_NEAREST:
        for int j = 0; j < newh; j++:
            for int i = 0; i < neww; i++:
                int nx = i * image->w / neww
                int ny = j * image->h / newh
                B c = image->data.b[nx + ny * image->w]
                scaled->data.b[i + j * neww] = c
    elif how == IST_BILINEAR:
        for int j = 0; j < newh; j++:
            for int i = 0; i < neww; i++:
                double nx = (double)i * (image->w - 1) / (neww - 1)
                double ny = (double)j * (image->h - 1) / (newh - 1)
                B c = getpixel_bilinear(image, nx, ny)
                scaled->data.b[i + j * neww] = c
    elif how == IST_BICUBIC:
        for int j = 0; j < newh; j++:
            for int i = 0; i < neww; i++:
                double nx = (double)i * (image->w - 1) / (neww - 1)
                double ny = (double)j * (image->h - 1) / (newh - 1)
                B c = getpixel_bicubic(image, nx, ny)
                scaled->data.b[i + j * neww] = c
    #elif how == IST_AVERAGE:
                
    return scaled

#def shift_multiply_image_inplace_f(V *image, int w, h, V shift, factor):
#    for int j = 0; j < h; j++:
#        for int i = 0; i < w; i++:
#            image[i + w * j] *= factor
#            image[i + w * j] += shift

V *def duplicate_image(V *image, int w, h):
    V *result = ALLOCATE(w * h * sizeof *image)
    memcpy(result, image, w * h * sizeof *image)
    return result

def shift_multiply_image(Image *image, V shift, factor):
    """
    Given an image, execute v = shift + v * factor for each pixel.
    """
    int w = image->w
    int h = image->h
    for int j = 0; j < h; j++:
        for int i = 0; i < w; i++:
            if image->type == IT_FLOAT:
                V v = image->data.v[i + j * w]
                v *= factor
                v += shift
                image->data.v[i + j * w] = v
            else:
                V v = image->data.b[i + j * w]
                v *= factor
                v += shift
                int c = v
                if v < 0: v = 0
                if v > 255: v = 255
                image->data.b[i + j * w] = c
        
def threshold_below(Image *image, int threshold, value):
    """
    Any pixels at or below the threshold are set to the given value.
    """
    int w = image->w
    int h = image->h
    for int j = 0; j < h; j++:
        for int i = 0; i < w; i++:
            int c = image->data.b[i + j * w]
            if c <= threshold:
                image->data.b[i + j * w] = value

def threshold_above(Image *image, int threshold, value):
    """
    Any pixels ar or above the given threshold are set to the given value.
    """
    int w = image->w
    int h = image->h
    for int j = 0; j < h; j++:
        for int i = 0; i < w; i++:
            int c = image->data.b[i + j * w]
            if c >= threshold:
                image->data.b[i + j * w] = value

def invert(Image *image):
    int w = image->w
    int h = image->h
    for int j = 0; j < h; j++:
        for int i = 0; i < w; i++:
            int c = image->data.b[i + j * w]
            image->data.b[i + j * w] = 255 - c

Image *def cumulate(Image *image, int cw, ch):
    """
    Make light areas lighter.
    """
    int w = image->w
    int h = image->h
    B *data = image->data.b
    Image *result = image_new_undef_b(w, h)
    B *a = result->data.b
    int ox = cw / 2
    int oy = ch / 2
    for int y = 0; y < h; y++:
        int ay, by
        if y < oy: ay = oy - y
        else: ay = 0
        if y + ch - oy > h:  by = h + oy - y
        else: by = ch
        for int x = 0; x < w; x++:
            int sum = 0
            int ax, bx
            if x < ox: ax = ox - x
            else: ax = 0
            if x + cw - ox > w:  bx = w + ox - x
            else: bx = cw
            for int j = ay; j < by; j++:
                for int i = ax; i < bx; i++:
                    sum += data[(y + j - oy) * w + (x + i - ox)]
            if sum > 255: sum = 255
            a[y * w + x] = sum
    return result

int def bounds_bigger_than_threshold(Image *image, int threshold, *x, *y, *x_, *y_):
    int w = image->w
    int h = image->h
    B *data = image->data.b
    int n= 0
    *x = w - 1
    *y = h - 1
    *x_ = 0
    *y_ = 0
    for int j = 0; j < h ; j++:
        for int i = 0; i < w; i++:
            B c = data[j * w + i]
            if c > threshold:
                if i < *x: *x = i
                if i > *x_: *x_ = i
                if j < *y: *y = j
                if j > *y_: *y_ = j
                n++
    return n

V *def new_image(int w, h):
    V *image = ALLOCATE(w * h * sizeof *image)
    return image

V *def binarize(V *image, int w, h, V threshold):
    V *b = malloc(w * h * sizeof *b)
    for int j = 0; j < h; j++:
        for int i = 0; i < w; i++:
            V c = image[i + w * j]
            if c >= threshold: c = 255
            else: c = 0
            b[i + w * j] = c
    return b

def stretch_min_max(Image *image, V newmin, newmax):
    if image->type == IT_FLOAT:
        V *ptr = image->data.v
        V minv = 1000000
        V maxv = -1000000
        for int j = 0; j < image->h; j++:
            for int i = 0; i < image->w; i++:
                V v = *ptr
                if v < minv: minv = v
                if v > maxv: maxv = v
                ptr++
            ptr += image->row - image->w
        printf("stretching from: [%f..%f]\n", minv, maxv)
        ptr = image->data.v
        for int j = 0; j < image->h; j++:
            for int i = 0; i < image->w; i++:
                V v = *ptr
                *ptr = newmin + (newmax - newmin) * (v - minv) / (maxv - minv)
                ptr++
            ptr += image->row - image->w
            

Image *def BtoV(Image const *image):
    int w = image->w
    int h = image->h
    Image *out = image_new_undef_f(w, h)
    for int j = 0; j < h; j++:
        for int i = 0; i < w; i++:
            out->data.v[i + w * j] = image->data.b[i + w * j]
    return out

Image *def BtoRGB(Image const *image):
    int w = image->w
    int h = image->h
    Image *out = image_new_undef_rgb(w, h)
    out->name = strdup(image->name)
    for int j = 0; j < h; j++:
        for int i = 0; i < w; i++:
            int b = image->data.b[i + w * j]
            Color c = {b, b, b}
            out->data.rgb[i + w * j] = c
    return out

Image *def VtoB(Image const *image):
    int w = image->w
    int h = image->h
    Image *raster = image_new_undef_b(w, h)
    for int j = 0; j < h; j++:
        for int i = 0; i < w; i++:
            int32_t c = image->data.v[i + w * j]
            if c < 0 or c > 255:
                fprintf(stderr, "Pixel %d truncated at %d/%d.\n", c, i, j)
                if c < 0: c = 0
                if c > 255: c = 255

            raster->data.b[i + w * j] = c
    return raster

Image *def image_load_b(char const *filename):
    char const *dot = strrchr(filename, '.')
    if not dot: return None
    if not strcmp(dot, ".tiff") or not strcmp(dot, ".tif"):
        return load_tiff_luminance(filename)
    elif not strcmp(dot, ".jpeg") or not strcmp(dot, ".jpg"):
        return load_jpeg_luminance(filename)
    return None

int def longest_horizontal_run(Image *image, int border, int threshold):
    int w = image->w
    int h = image->h
    int maxrun = 0
    for int j = border; j < h - border; j++:
        int run = 0
        for int i = border; i < w - border; i++:
            if image->data.b[i + j * w] <= threshold:
                run++
                if run > maxrun: maxrun = run
            else: run = 0
    return maxrun

int def longest_vertical_run(Image *image, int border, int threshold):
    int w = image->w
    int h = image->h
    int maxrun = 0
    for int i = border; i < w - border; i++:
        int run = 0
        for int j = border; j < h - border; j++:
            if image->data.b[i + j * w] <= threshold:
                run++
                if run > maxrun: maxrun = run
            else: run = 0
    return maxrun

static char prefix[MAX_PATH] = "./"
static char basename[MAX_PATH] = ""

def set_output_prefix(char const *new_prefix):
    strcpy(prefix, new_prefix)

def set_output_basename(char const *new_basename):
    strcpy(basename, new_basename)

def get_image_name(Image const *image, char const *name, char *out):
    char const *base = image->name
    if basename[0]:
        base = basename
    sprintf(out, "%s/%s_%s.tiff", prefix, base, name)

def get_image_name_algo_ext(Image const *image, char const *name, char const *algo,
    char const *ext, char *out):
    char const *base = image->name
    if basename[0]:
        base = basename
    if algo and algo[0]:
        sprintf(out, "%s/%s_%s_%s.%s", prefix, base, name, algo, ext)
    else:
        get_image_name(image, name, out)
        
def get_image_name_algo(Image const *image, char const *name, char const *algo,
    char *out):
    return get_image_name_algo_ext(image, name, algo, "tiff", out)

def get_image_name_algo_key_ext(Image const *image, char const *name,
    char const *algo, char const *key, char const *ext, char *out):
    char const *base = image->name
    if basename[0]:
        base = basename
    if key and key[0]:
        sprintf(out, "%s/%s_%s_%s:%s.%s", prefix, base, name, algo, key, ext)
    else:
        get_image_name_algo_ext(image, name, algo, ext, out)

def get_image_name_algo_key(Image const *image, char const *name,
    char const *algo, char const *key, char *out):
    return get_image_name_algo_key_ext(image, name, algo, key, "tiff", out)