/* MBrotScan.c
 * Fast parallel Mandelbrot set scanner
 * By Neil A Carson, RiscBSD kernel team, 1996 A.D.
 */

/* Includes
 */
#include <ctype.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "MBrot.h"

/* Number of blocks across the screen
 */
const int BLOCKS_S = (SCREEN_S/BLOCK_SIZE);

/* Start of screen memory
 */
char *screen;

/* Data from the CLI
 */
struct
{
    float x;
    float y;
    float s;
    int maxiter;
} CLI_data;
result_data_t *result_data;

#define GET_ADDRESS(x,y) (screen + (y*SCREEN_W) + x)

/* Copy a row of the screen
 */
void copy_row(int from_y, int to_y)
{
    void *from, *to;

    if (from_y < 0) return;
    if (to_y < 0) return;
    if (from_y >= SCREEN_S) return;
    if (to_y >= SCREEN_S) return;
    from = GET_ADDRESS(0, from_y);
    to = GET_ADDRESS(0, to_y);
    memcpy(to, from, SCREEN_S);
}

/* Copy current screen - if needed - to account for reflection
 * A bit slow as it uses memcpy() - well I suppose that depends upon the given
 * implementation of memcpy() !
 */
void reflect_screen(int pixel_row_about)
{
    /* Reflect top half...
     */
    if (pixel_row_about >= (SCREEN_S >> 1))
    {
        int dist;

        dist = (SCREEN_S - pixel_row_about) - 1;
        while (dist > 0)
        {
            copy_row(pixel_row_about - dist, pixel_row_about + dist);
            dist --;
        }
    }
    else
    {
        int dist;

        dist = pixel_row_about;
        while (dist > 0)
        {
            copy_row(pixel_row_about + dist, pixel_row_about - dist);
            dist --;
        }
    }
}

/* Create the data necessary to iterate a Mandelbrot set on the multiprocessor
 * system. Extend this to include X-axis reflection later on.
 */
void ready_to_iterate(void)
{
    int min_y_line, max_y_line, min_y_block, max_y_block, reflecting;
    int block_no, int_x, int_y, reflection_line, max_blocks, cnt;
    float x, y, block_size, min_x, min_y, max_x, max_y;
    float mandel_to_pixels;

    /* Get memory to put results in, adding on an extra one to mark as a
     * terminator.
     */
    max_blocks = (BLOCKS_S * BLOCKS_S) + 1;
    result_data = veneer_get_result(max_blocks * sizeof(result_data_t));
    if (result_data == 0)
        Exit_Error("No memory for results, must quit now!");
    memset(result_data, 0xFF, max_blocks * sizeof(result_data_t));

    /* Initial calculations
     */
    block_size = CLI_data.s / (float) BLOCKS_S;
    min_x = CLI_data.x;
    min_y = CLI_data.y;
    max_x = min_x + CLI_data.s;
    max_y = min_y + CLI_data.s;
    block_no = 0;

    /* Can we reflect...? If so, calculate reflection_line. Remember, we
     * *need* to be able to reflect from the top or the bottom, so calculate
     * whether the reflection line is nearest the top or the bottom. If the
     * top, then calculate blocks below, if not the boxes above.
     */
    if ((min_y < 0) && (max_y > 0))               /* We can reflect! */
    {
        /* Calculate Mandelbrot -> Screen pixels scalar and reflection line
         */
        mandel_to_pixels = (float) SCREEN_S / (max_y - min_y);
        reflection_line = (int) (fabs(min_y) * mandel_to_pixels);

        /* Calculate bounds to plot
         */
        if (reflection_line < (SCREEN_S >> 1))
        {
            max_y_line = SCREEN_S;
            min_y_line = reflection_line;
        }
        else
        {
            max_y_line = reflection_line;
            min_y_line = 0;
        }
        reflecting = 1;
    }
    else
    {
        min_y_line = 0;
        max_y_line = SCREEN_S;
        reflecting = 0;
    }

    /* Calculate the block offsets
     */
    min_y_block = (min_y_line / BLOCK_SIZE) * BLOCK_SIZE;
    max_y_block = (max_y_line / BLOCK_SIZE) * BLOCK_SIZE;

    /* Clip and extend if needed
     */
    if (min_y_block >= 9) min_y_block -= 9;
    if (max_y_block < (SCREEN_S - 9)) max_y_block += 9;

    /* Fill in table, ready to let go of the data to the parallel calculator.
     */
    if (reflecting) y = ((float) min_y_block / mandel_to_pixels) + min_y;
        else y = min_y;

    for (int_y = min_y_block; int_y < max_y_block; int_y += BLOCK_SIZE)
    {
        x = CLI_data.x;
        for(int_x = 0; int_x < SCREEN_S; int_x += BLOCK_SIZE)
        {
            result_data[block_no].calculated = 0;
            result_data[block_no].x = (int) (x * (float) SHIFT);
            result_data[block_no].y = (int) (y * (float) SHIFT);
            result_data[block_no].s = (int) (block_size * (float) SHIFT);
            result_data[block_no].bx = int_x;
            result_data[block_no].by = int_y;
            result_data[block_no].bs = BLOCK_SIZE - 1;
            block_no ++;
            x += block_size;
        }
        y += block_size;
    }

    /* Mark the next block as the end!
     */
    result_data[block_no].x = -1;

    /* Black---out the screen
     */
    memset(screen, 0, SCREEN_W * SCREEN_H);

    /* Now set the veneer iterating...
     */
    veneer_fork(block_no, CLI_data.maxiter, result_data);

    /* Delay while the Hydra catches up :-)
     */
    while (!veneer_finished()) {};

    /* Now reflect about the centre
     */
    if (reflecting) reflect_screen(reflection_line);
}

/* Main program
 */
int main(int argc, char **argv)
{
    /* Check CLI arguments
     */
    if (argc != 5)
    {
        printf("Usage: nacbrot x y size maxiter\n");
        exit(0);
    }

    /* Get CLI data
     */
    CLI_data.x = (float) atof(argv[1]);
    CLI_data.y = (float) atof(argv[2]);
    CLI_data.s = (float) atof(argv[3]);
    CLI_data.maxiter = atoi(argv[4]);

    /* Start the veneer, in low or high precision mode
     */
    veneer_start(CLI_data.s <= SIZE_THRESH);

    /* Change screen mode
     */
    screen = veneer_change_mode();

    /* Iterate
     */
    ready_to_iterate();

    /* Close down the veneer
     */
    veneer_close_down();
}
