module main;

import std.stdio;
import std.json;
import std.file;
import std.conv;
import std.string;
import gamut;
import expression;

void usage()
{
    stderr.writeln();
    stderr.writeln("Decoupe-Voiture");
    stderr.writeln("l'indispensable outil de decoupe de voiture\n");
    stderr.writeln("usage: decoupe-voiture input.png [-p <pattern>] output.png\n");
    stderr.writeln();
    stderr.writeln("Arguments:");
    stderr.writeln("    -p            Pattern to execute, default = classic_complete_48x48");
    stderr.writeln("                  See patterns/ for availability.");
    stderr.writeln("    --help        Shows this help.");
    stderr.writeln();
}


int main(string[] args)
{
    try
    {
        string input = null;
        string output = null;
        string pattern = "classic_complete_48x48";
        bool showHelp = false;

        for(int i = 1; i < args.length; ++i)
        {
            string arg = args[i];
            if (arg == "-p" || arg == "--pattern")
            {
                ++i;
                pattern = args[i];
            }
            else if (arg == "-h" || arg == "--help")
            {
                showHelp = true;
            }
            else
            {
                if (input)
                {
                    if (output)
                        throw new Exception("Too many files provided");
                    else
                        output = arg;
                }
                else
                    input = arg;
            }
        }

        if (showHelp || input is null || output is null)
        {
            usage();
            return 0;
        }

        processFile(input, output, pattern);
    }
    catch(Exception e)
    {
        writefln("error: %s", e.message);
        usage();
        return 1;
    }
    return 0;
}

struct Point
{
    int x, y;
}

struct Size
{
    int w, h;
}

struct Rect
{
    Point pos;
    Size size;
}

void processFile(string inputPath, string outputPath, string pattern)
{
    alias jsonTrue = JSONType.true_;
    alias jsonFalse = JSONType.false_;

    // try to open pattern file

    string patternPath = "patterns/" ~ pattern ~ ".json";

    JSONValue patternFile = parseJSON(cast(string)(std.file.read(patternPath)));

    Image input;
    input.loadFromFile(inputPath);
    if (!input.isValid)
        throw new Exception("cant load " ~ inputPath);

    input.convertTo(PixelType.rgba8);
    
    Size defaultSize = parseSize(patternFile, "default-size", Size(16, 16));

    Size inputSize = parseSize(patternFile, "input-size", defaultSize);
    Size outputSize = parseSize(patternFile, "output-size", defaultSize);

    if (inputSize.w != input.width || inputSize.h != input.height)
        throw new Exception("input size mismatch");

    Image output;
    output.create(outputSize.w, outputSize.h, PixelType.rgba8);


    void copyRect(Rect src, Rect dst)
    {
        rectInsideImage(input, src);
        rectInsideImage(output, dst);

        int w = src.size.w;
        int h = src.size.h;

        for (int y = 0; y < h; ++y)
        {
            ubyte[] srcScan = cast(ubyte[])  input.scanline(src.pos.y + y);
            ubyte[] dstScan = cast(ubyte[]) output.scanline(dst.pos.y + y);
            int bytes = w * 4;
            int ii    = 4 * src.pos.x;
            int oo    = 4 * dst.pos.x;
            dstScan[oo .. oo+bytes] = srcScan[ii .. ii+bytes];
        }
    }

    foreach(JSONValue tr; patternFile["copyRects"].array)
    {
        Rect from = parseRect(tr, "from", defaultSize);
        Rect to   = parseRect(tr,   "to", defaultSize);

        // If from rectangle is smaller than to rectangle,
        // center the dest rectangle inside the given destination

        if (from.size.w < to.size.w)
        {
            int marginX = (to.size.w - from.size.w) / 2;
            to.pos.x += marginX;
            to.size.w = from.size.w;
        }
        if (from.size.h < to.size.h)
        {
            int marginY = (to.size.h - from.size.h) / 2;
            to.pos.y += marginY;
            to.size.h = from.size.h;
        }

        if (from.size != to.size)
            throw new Exception(format("rectangle size mismatch for key: %s", tr));

        //writefln("DEBUG: copy %s to %s", from, to);

        copyRect(from, to);
    }
    writeln("All rectangles copied");

    int encodeFlags = 0;
    bool r = output.saveToFile(outputPath, encodeFlags);
    if (!r)
    {
        throw new Exception("Couldn't save file " ~ outputPath);
    }
}

void rectInsideImage(ref Image i, Rect r)
{
    if (r.pos.x < 0)
        throw new Exception("Rectangle x is < 0");
    if (r.pos.y < 0)
        throw new Exception("Rectangle y is < 0");
    if (r.size.w < 0)
        throw new Exception("Rectangle width is < 0");
    if (r.size.h < 0)
        throw new Exception("Rectangle height is < 0");
    if (r.pos.x + r.size.w > i.width)
        throw new Exception("Rectangle exceeds width of image");
    if (r.pos.y + r.size.h > i.height)
        throw new Exception("Rectangle exceeds height of image");
}

Point parsePoint(JSONValue parent, string keyName, Size defaultSize)
{
    string r = parent[keyName].str;
    return parsePoint(r, defaultSize);
}

Point parsePoint(string r, Size defaultSize)
{
    r = strip(r);
    int cPos = cast(int) r.indexOf(",");
    if (cPos == -1)
        throw new Exception("Point should follow this in format: 4,5");
    int x = parseIntegerExpression(r[0..cPos], defaultSize);
    int y = parseIntegerExpression(r[cPos+1..$], defaultSize);
    return Point(x, y);
}

Size parseSize(JSONValue parent, string keyName, Size defaultSize)
{
    return parseSize(parent[keyName].str, defaultSize);
}

Size parseSize(string r, Size defaultSize)
{    
    r = strip(r);
    int xPos = cast(int) r.indexOf("x");
    if (xPos == -1)
        throw new Exception("Size should follow this in format: 48x48");
    int w = parseIntegerExpression(r[0..xPos], defaultSize);
    int h = parseIntegerExpression(r[xPos+1..$], defaultSize);
    return Size(w, h);
}

Rect parseRect(JSONValue parent, 
               string keyName,
               Size defaultSize)
{
    string r = parent[keyName].str;
    return parseRect(r, defaultSize);
}

Rect parseRect(string r, Size defaultSize)
{
    r = strip(r);
    int mPos = cast(int) r.indexOf("--");

    Size size;
    Point pos;

    if (mPos == -1)
    {
        pos = parsePoint(r, defaultSize);
        size = defaultSize;        
    }
    else
    {
        // U+2014 is 3 bytes long
        pos = parsePoint(r[0..mPos], defaultSize);
        size = parseSize(r[mPos+2..$], defaultSize);
    }
    
    return Rect(pos, size);
}

int parseIntegerExpression(string source, 
                           Size defaultSize)
{
    source = strip(source);
    auto e = compileExpression!int(source);
    //e["TX"] = defaultSize.w;
    //e["TY"] = defaultSize.h;
    return e();
}