/*
 * printing.c: Cross-platform printing manager. Handles document
 * setup and layout.
 */

#include <assert.h>

#include "puzzles.h"

struct puzzle {
    const game *game;
    game_params *par;
    game_ui *ui;
    game_state *st;
    game_state *st2;
};

struct document {
    int pw, ph;
    int npuzzles;
    struct puzzle *puzzles;
    int puzzlesize;
    bool got_solns;
    float *colwid, *rowht;
    float userscale;
};

/*
 * Create a new print document. pw and ph are the layout
 * parameters: they state how many puzzles will be printed across
 * the page, and down the page.
 */
document *document_new(int pw, int ph, float userscale)
{
    document *doc = snew(document);

    doc->pw = pw;
    doc->ph = ph;
    doc->puzzles = NULL;
    doc->puzzlesize = doc->npuzzles = 0;
    doc->got_solns = false;

    doc->colwid = snewn(pw, float);
    doc->rowht = snewn(ph, float);

    doc->userscale = userscale;

    return doc;
}

/*
 * Free a document structure, whether it's been printed or not.
 */
void document_free(document *doc)
{
    int i;

    for (i = 0; i < doc->npuzzles; i++) {
	doc->puzzles[i].game->free_params(doc->puzzles[i].par);
	doc->puzzles[i].game->free_ui(doc->puzzles[i].ui);
	doc->puzzles[i].game->free_game(doc->puzzles[i].st);
	if (doc->puzzles[i].st2)
	    doc->puzzles[i].game->free_game(doc->puzzles[i].st2);
    }

    sfree(doc->colwid);
    sfree(doc->rowht);

    sfree(doc->puzzles);
    sfree(doc);
}

/*
 * Called from midend.c to add a puzzle to be printed. Provides a
 * game_params (for initial layout computation), a game_state, and
 * optionally a second game_state to be printed in parallel on
 * another sheet (typically the solution to the first game_state).
 */
void document_add_puzzle(document *doc, const game *game, game_params *par,
			 game_ui *ui, game_state *st, game_state *st2)
{
    if (doc->npuzzles >= doc->puzzlesize) {
	doc->puzzlesize += 32;
	doc->puzzles = sresize(doc->puzzles, doc->puzzlesize, struct puzzle);
    }
    doc->puzzles[doc->npuzzles].game = game;
    doc->puzzles[doc->npuzzles].par = par;
    doc->puzzles[doc->npuzzles].ui = ui;
    doc->puzzles[doc->npuzzles].st = st;
    doc->puzzles[doc->npuzzles].st2 = st2;
    doc->npuzzles++;
    if (st2)
	doc->got_solns = true;
}

static void get_puzzle_size(const document *doc, struct puzzle *pz,
			    float *w, float *h, float *scale)
{
    float ww, hh, ourscale;

    /* Get the preferred size of the game, in mm. */
    {
        game_ui *ui = pz->game->new_ui(pz->st);
        pz->game->print_size(pz->par, ui, &ww, &hh);
        pz->game->free_ui(ui);
    }

    /* Adjust for user-supplied scale factor. */
    ourscale = doc->userscale;

    /*
     * FIXME: scale it down here if it's too big for the page size.
     * Rather than do complicated things involving scaling all
     * columns down in proportion, the simplest approach seems to
     * me to be to scale down until the game fits within one evenly
     * divided cell of the page (i.e. width/pw by height/ph).
     * 
     * In order to do this step we need the page size available.
     */

    *scale = ourscale;
    *w = ww * ourscale;
    *h = hh * ourscale;
}

/*
 * Calculate the the number of pages for a document.
 */
int document_npages(const document *doc)
{
    int ppp;			       /* puzzles per page */
    int pages, passes;

    ppp = doc->pw * doc->ph;
    pages = (doc->npuzzles + ppp - 1) / ppp;
    passes = (doc->got_solns ? 2 : 1);

    return pages * passes;
}

/*
 * Begin a document.
 */
void document_begin(const document *doc, drawing *dr)
{
    print_begin_doc(dr, document_npages(doc));
}

/*
 * End a document.
 */
void document_end(const document *doc, drawing *dr)
{
    print_end_doc(dr);
}

/*
 * Print a single page of a document.
 */
void document_print_page(const document *doc, drawing *dr, int page_nr)
{
    int ppp;			       /* puzzles per page */
    int pages;
    int page, pass;
    int pageno;
    int i, n, offset;
    float colsum, rowsum;

    ppp = doc->pw * doc->ph;
    pages = (doc->npuzzles + ppp - 1) / ppp;

    /* Get the current page, pass, and pageno based on page_nr. */
    if (page_nr < pages) {
        page = page_nr;
        pass = 0;
    }
    else {
        assert(doc->got_solns);
        page = page_nr - pages;
        pass = 1;
    }
    pageno = page_nr + 1;

    offset = page * ppp;
    n = min(ppp, doc->npuzzles - offset);

    print_begin_page(dr, pageno);

    for (i = 0; i < doc->pw; i++)
        doc->colwid[i] = 0;
    for (i = 0; i < doc->ph; i++)
        doc->rowht[i] = 0;

    /*
     * Lay the page out by computing all the puzzle sizes.
     */
    for (i = 0; i < n; i++) {
        struct puzzle *pz = doc->puzzles + offset + i;
        int x = i % doc->pw, y = i / doc->pw;
        float w, h, scale;

        get_puzzle_size(doc, pz, &w, &h, &scale);

        /* Update the maximum width/height of this column. */
        doc->colwid[x] = max(doc->colwid[x], w);
        doc->rowht[y] = max(doc->rowht[y], h);
    }

    /*
     * Add up the maximum column/row widths to get the
     * total amount of space used up by puzzles on the
     * page. We will use this to compute gutter widths.
     */
    colsum = 0.0;
    for (i = 0; i < doc->pw; i++)
        colsum += doc->colwid[i];
    rowsum = 0.0;
    for (i = 0; i < doc->ph; i++)
        rowsum += doc->rowht[i];

    /*
     * Now do the printing.
     */
    for (i = 0; i < n; i++) {
        struct puzzle *pz = doc->puzzles + offset + i;
        int x = i % doc->pw, y = i / doc->pw, j;
        float w, h, scale, xm, xc, ym, yc;
        int pixw, pixh, tilesize;

        if (pass == 1 && !pz->st2)
            continue;	       /* nothing to do */

        /*
         * The total amount of gutter space is the page
         * width minus colsum. This is divided into pw+1
         * gutters, so the amount of horizontal gutter
         * space appearing to the left of this puzzle
         * column is
         *
         *   (width-colsum) * (x+1)/(pw+1)
         * = width * (x+1)/(pw+1) - (colsum * (x+1)/(pw+1))
         */
        xm = (float)(x+1) / (doc->pw + 1);
        xc = -xm * colsum;
        /* And similarly for y. */
        ym = (float)(y+1) / (doc->ph + 1);
        yc = -ym * rowsum;

        /*
         * However, the amount of space to the left of this
         * puzzle isn't just gutter space: we must also
         * count the widths of all the previous columns.
         */
        for (j = 0; j < x; j++)
            xc += doc->colwid[j];
        /* And similarly for rows. */
        for (j = 0; j < y; j++)
            yc += doc->rowht[j];

        /*
         * Now we adjust for this _specific_ puzzle, which
         * means centring it within the cell we've just
         * computed.
         */
        get_puzzle_size(doc, pz, &w, &h, &scale);
        xc += (doc->colwid[x] - w) / 2;
        yc += (doc->rowht[y] - h) / 2;

        /*
         * And now we know where and how big we want to
         * print the puzzle, just go ahead and do so. For
         * the moment I'll pick a standard pixel tile size
         * of 512.
         *
         * (FIXME: would it be better to pick this value
         * with reference to the printer resolution? Or
         * permit each game to choose its own?)
         */
        tilesize = 512;
        pz->game->compute_size(pz->par, tilesize, pz->ui, &pixw, &pixh);
        print_begin_puzzle(dr, xm, xc, ym, yc, pixw, pixh, w, scale);
        pz->game->print(dr, pass == 0 ? pz->st : pz->st2, pz->ui, tilesize);
        print_end_puzzle(dr);
    }

    print_end_page(dr, pageno);
}

/*
 * Having accumulated a load of puzzles, actually do the printing.
 */
void document_print(const document *doc, drawing *dr)
{
    int page, pages;
    pages = document_npages(doc);
    print_begin_doc(dr, pages);
    for (page = 0; page < pages; page++)
        document_print_page(doc, dr, page);
    print_end_doc(dr);
}
