#include "main.hpp"


#define SHIFT(n) do { argc -= n; argv += n; } while (0)


static void usage(const char* name=NULL) {
    LOG("Usage:   %s [--dim=...] [--tint=...] [--fulltint=...] [--dist=avg|euclid|lab] input.jpg output.jpg sample_1.jpg... ", name ?: "");
    LOG("Example: %s ~/Pictures/foo/input.jpg mosaic.jpg ~/Pictures/foo/*.jpg", name ?: "");
    exit(1);
}


int main(int argc, const char** argv) {
    SHIFT(1);
    int args = parse_args(argc, argv);
    if (args < 0) usage();
    SHIFT(args);

    if (!argc) usage();
    Image* in = JpegImage::in(argv[0]);
    if (!in) {
        return 1;
    }
    SHIFT(1);

    if (!argc) usage();
    const char* outfn = argv[0];
    SHIFT(1);

    Mosaics mosaics;
    while (argc) {
        Image* im = JpegImage::in(argv[0]);
        if (im) {
            if (!mosaics.add(im)) {
                LOG("Ignoring '%s'", argv[0]);
            }
            delete im;
        }
        SHIFT(1);
    }

    if (!mosaics.size()) {
        LOG("No input files");
        return 1;
    }

    if (in->buf.w < config.mosaic_dim || in->buf.h < config.mosaic_dim) {
        LOG("Input file too small");
        return 1;
    }
    Image* out = new Image((in->buf.w/config.mosaic_dim)*config.mosaic_dim, (in->buf.h/config.mosaic_dim)*config.mosaic_dim);

    for (unsigned boxx=0; boxx<in->buf.w/config.mosaic_dim; ++boxx) {
        LOG("Matching %u/%u ...", boxx, in->buf.w/config.mosaic_dim);
        for (unsigned boxy=0; boxy<in->buf.h/config.mosaic_dim; ++boxy) {
            #if 1 // pixel-wise distances, slow
                Image* in_box = in->boxed(boxx*config.mosaic_dim, boxy*config.mosaic_dim, config.mosaic_dim, config.mosaic_dim);
                Image* in_box_half[] = {
                    in->boxed((boxx*config.mosaic_dim), (boxy*config.mosaic_dim), config.mosaic_dim_half, config.mosaic_dim_half),
                    in->boxed((boxx*config.mosaic_dim)+config.mosaic_dim_half, (boxy*config.mosaic_dim), config.mosaic_dim_half, config.mosaic_dim_half),
                    in->boxed((boxx*config.mosaic_dim), (boxy*config.mosaic_dim)+config.mosaic_dim_half, config.mosaic_dim_half, config.mosaic_dim_half),
                    in->boxed((boxx*config.mosaic_dim)+config.mosaic_dim_half, (boxy*config.mosaic_dim)+config.mosaic_dim_half, config.mosaic_dim_half, config.mosaic_dim_half)
                };

                double dist_full;
                const Image* best_full = mosaics.find_best(in_box, dist_full);

                double dist_half[4];
                const Image* best_half[] = {
                    mosaics.find_best(in_box_half[0], dist_half[0]),
                    mosaics.find_best(in_box_half[1], dist_half[1]),
                    mosaics.find_best(in_box_half[2], dist_half[2]),
                    mosaics.find_best(in_box_half[3], dist_half[3]),
                };
                unsigned dist_half_avg = (dist_half[0] + dist_half[1] + dist_half[2] + dist_half[3]) / 4.0;

                rgb_t avg_full = in_box->med();
                rgb_t avg_half[] = {in_box_half[0]->med(), in_box_half[1]->med(), in_box_half[2]->med(), in_box_half[3]->med()};
            #else // average/median based distances, fast
                rgb_t avg_full = in->med(boxx*config.mosaic_dim, boxy*config.mosaic_dim, config.mosaic_dim, config.mosaic_dim);
                rgb_t avg_half[] = {
                    in->med((boxx*config.mosaic_dim), (boxy*config.mosaic_dim), config.mosaic_dim_half, config.mosaic_dim_half),
                    in->med((boxx*config.mosaic_dim)+config.mosaic_dim_half, (boxy*config.mosaic_dim), config.mosaic_dim_half, config.mosaic_dim_half),
                    in->med((boxx*config.mosaic_dim), (boxy*config.mosaic_dim)+config.mosaic_dim_half, config.mosaic_dim_half, config.mosaic_dim_half),
                    in->med((boxx*config.mosaic_dim)+config.mosaic_dim_half, (boxy*config.mosaic_dim)+config.mosaic_dim_half, config.mosaic_dim_half, config.mosaic_dim_half)
                };

                unsigned char dist_full;
                const Image* best_full = mosaics.find_best(config.mosaic_dim, avg_full, dist_full);

                unsigned char dist_half[4];
                const Image* best_half[] = {
                    mosaics.find_best(config.mosaic_dim_half, avg_half[0], dist_half[0]),
                    mosaics.find_best(config.mosaic_dim_half, avg_half[1], dist_half[1]),
                    mosaics.find_best(config.mosaic_dim_half, avg_half[2], dist_half[2]),
                    mosaics.find_best(config.mosaic_dim_half, avg_half[3], dist_half[3])
                };
                unsigned dist_half_avg = ((unsigned)dist_half[0] + (unsigned)dist_half[1] + (unsigned)dist_half[2] + (unsigned)dist_half[3]) / 4;
            #endif

            if (dist_half_avg < dist_full && !(best_half[0] == best_half[1] && best_half[0] == best_half[2] && best_half[0] == best_half[3])) {
                out->put((boxx*config.mosaic_dim), (boxy*config.mosaic_dim), best_half[0]);
                out->put((boxx*config.mosaic_dim)+config.mosaic_dim_half, (boxy*config.mosaic_dim), best_half[1]);
                out->put((boxx*config.mosaic_dim), (boxy*config.mosaic_dim)+config.mosaic_dim_half, best_half[2]);
                out->put((boxx*config.mosaic_dim)+config.mosaic_dim_half, (boxy*config.mosaic_dim)+config.mosaic_dim_half, best_half[3]);
                if (config.tint) {
                    out->tint((boxx*config.mosaic_dim), (boxy*config.mosaic_dim), config.mosaic_dim_half, config.mosaic_dim_half, avg_half[0], config.tint);
                    out->tint((boxx*config.mosaic_dim)+config.mosaic_dim_half, (boxy*config.mosaic_dim), config.mosaic_dim_half, config.mosaic_dim_half, avg_half[1], config.tint);
                    out->tint((boxx*config.mosaic_dim), (boxy*config.mosaic_dim)+config.mosaic_dim_half, config.mosaic_dim_half, config.mosaic_dim_half, avg_half[2], config.tint);
                    out->tint((boxx*config.mosaic_dim)+config.mosaic_dim_half, (boxy*config.mosaic_dim)+config.mosaic_dim_half, config.mosaic_dim_half, config.mosaic_dim_half, avg_half[3], config.tint);
                }
            } else {
                out->put(boxx*config.mosaic_dim, boxy*config.mosaic_dim, best_full);
                if (config.tint) {
                    out->tint(boxx*config.mosaic_dim, boxy*config.mosaic_dim, config.mosaic_dim, config.mosaic_dim, avg_full, config.tint);
                }
            }

            #if 1
                delete in_box;
                delete in_box_half[0];
                delete in_box_half[1];
                delete in_box_half[2];
                delete in_box_half[3];
            #endif
        }
    }
    if (config.full_tint) {
        out->tint(in, config.full_tint);
    }

    JpegImage::out(outfn, out);

    delete in;
    delete out;
    return 0;
}