/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id: mandelbrot.c 18338 2008-08-23 09:46:38Z mcuelenaere $
 *
 * Copyright (C) 2004 Matthias Wientapper
 * Heavily extended 2005 Jens Arnold
 *
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/
#include "plugin.h"

#ifdef HAVE_LCD_BITMAP
#include "grey.h"
#include "xlcd.h"

PLUGIN_HEADER

/* variable button definitions */
#if CONFIG_KEYPAD == RECORDER_PAD
#define MANDELBROT_QUIT BUTTON_OFF
#define MANDELBROT_UP BUTTON_UP
#define MANDELBROT_DOWN BUTTON_DOWN
#define MANDELBROT_LEFT BUTTON_LEFT
#define MANDELBROT_RIGHT BUTTON_RIGHT
#define MANDELBROT_ZOOM_IN BUTTON_PLAY
#define MANDELBROT_ZOOM_OUT BUTTON_ON
#define MANDELBROT_MAXITER_INC BUTTON_F2
#define MANDELBROT_MAXITER_DEC BUTTON_F1
#define MANDELBROT_RESET BUTTON_F3

#elif CONFIG_KEYPAD == ARCHOS_AV300_PAD
#define MANDELBROT_QUIT BUTTON_OFF
#define MANDELBROT_UP BUTTON_UP
#define MANDELBROT_DOWN BUTTON_DOWN
#define MANDELBROT_LEFT BUTTON_LEFT
#define MANDELBROT_RIGHT BUTTON_RIGHT
#define MANDELBROT_ZOOM_IN BUTTON_SELECT
#define MANDELBROT_ZOOM_OUT BUTTON_ON
#define MANDELBROT_MAXITER_INC BUTTON_F2
#define MANDELBROT_MAXITER_DEC BUTTON_F1
#define MANDELBROT_RESET BUTTON_F3

#elif CONFIG_KEYPAD == ONDIO_PAD
#define MANDELBROT_QUIT BUTTON_OFF
#define MANDELBROT_UP BUTTON_UP
#define MANDELBROT_DOWN BUTTON_DOWN
#define MANDELBROT_LEFT BUTTON_LEFT
#define MANDELBROT_RIGHT BUTTON_RIGHT
#define MANDELBROT_ZOOM_IN_PRE BUTTON_MENU
#define MANDELBROT_ZOOM_IN (BUTTON_MENU | BUTTON_REL)
#define MANDELBROT_ZOOM_IN2 (BUTTON_MENU | BUTTON_UP)
#define MANDELBROT_ZOOM_OUT (BUTTON_MENU | BUTTON_DOWN)
#define MANDELBROT_MAXITER_INC (BUTTON_MENU | BUTTON_RIGHT)
#define MANDELBROT_MAXITER_DEC (BUTTON_MENU | BUTTON_LEFT)
#define MANDELBROT_RESET (BUTTON_MENU | BUTTON_OFF)

#elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \
      (CONFIG_KEYPAD == IRIVER_H300_PAD)
#define MANDELBROT_QUIT BUTTON_OFF
#define MANDELBROT_UP BUTTON_UP
#define MANDELBROT_DOWN BUTTON_DOWN
#define MANDELBROT_LEFT BUTTON_LEFT
#define MANDELBROT_RIGHT BUTTON_RIGHT
#define MANDELBROT_ZOOM_IN BUTTON_SELECT
#define MANDELBROT_ZOOM_OUT BUTTON_MODE
#define MANDELBROT_MAXITER_INC (BUTTON_ON | BUTTON_RIGHT)
#define MANDELBROT_MAXITER_DEC (BUTTON_ON | BUTTON_LEFT)
#define MANDELBROT_RESET BUTTON_REC

#define MANDELBROT_RC_QUIT BUTTON_RC_STOP

#elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
      (CONFIG_KEYPAD == IPOD_3G_PAD) || \
      (CONFIG_KEYPAD == IPOD_1G2G_PAD)
#define MANDELBROT_QUIT (BUTTON_SELECT | BUTTON_MENU)
#define MANDELBROT_UP BUTTON_MENU
#define MANDELBROT_DOWN BUTTON_PLAY
#define MANDELBROT_LEFT BUTTON_LEFT
#define MANDELBROT_RIGHT BUTTON_RIGHT
#define MANDELBROT_ZOOM_IN BUTTON_SCROLL_FWD
#define MANDELBROT_ZOOM_OUT BUTTON_SCROLL_BACK
#define MANDELBROT_MAXITER_INC (BUTTON_SELECT | BUTTON_RIGHT)
#define MANDELBROT_MAXITER_DEC (BUTTON_SELECT | BUTTON_LEFT)
#define MANDELBROT_RESET (BUTTON_SELECT | BUTTON_PLAY)

#elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD
#define MANDELBROT_QUIT            BUTTON_POWER
#define MANDELBROT_UP              BUTTON_UP
#define MANDELBROT_DOWN            BUTTON_DOWN
#define MANDELBROT_LEFT            BUTTON_LEFT
#define MANDELBROT_RIGHT           BUTTON_RIGHT
#define MANDELBROT_ZOOM_IN_PRE     BUTTON_SELECT
#define MANDELBROT_ZOOM_IN         (BUTTON_SELECT | BUTTON_REL)
#define MANDELBROT_ZOOM_OUT_PRE    BUTTON_SELECT
#define MANDELBROT_ZOOM_OUT        (BUTTON_SELECT | BUTTON_REPEAT)
#define MANDELBROT_MAXITER_INC_PRE BUTTON_PLAY
#define MANDELBROT_MAXITER_INC     (BUTTON_PLAY | BUTTON_REL)
#define MANDELBROT_MAXITER_DEC_PRE BUTTON_PLAY
#define MANDELBROT_MAXITER_DEC     (BUTTON_PLAY | BUTTON_REPEAT)
#define MANDELBROT_RESET           BUTTON_REC

#elif CONFIG_KEYPAD == GIGABEAT_PAD
#define MANDELBROT_QUIT         BUTTON_POWER
#define MANDELBROT_UP           BUTTON_UP
#define MANDELBROT_DOWN         BUTTON_DOWN
#define MANDELBROT_LEFT         BUTTON_LEFT
#define MANDELBROT_RIGHT        BUTTON_RIGHT
#define MANDELBROT_ZOOM_IN_PRE  BUTTON_SELECT
#define MANDELBROT_ZOOM_IN      (BUTTON_SELECT | BUTTON_REL)
#define MANDELBROT_ZOOM_OUT_PRE BUTTON_SELECT
#define MANDELBROT_ZOOM_OUT     (BUTTON_SELECT | BUTTON_REPEAT)
#define MANDELBROT_MAXITER_INC  BUTTON_VOL_UP
#define MANDELBROT_MAXITER_DEC  BUTTON_VOL_DOWN
#define MANDELBROT_RESET        BUTTON_A

#elif CONFIG_KEYPAD == SANSA_E200_PAD
#define MANDELBROT_QUIT BUTTON_POWER
#define MANDELBROT_UP BUTTON_UP
#define MANDELBROT_DOWN BUTTON_DOWN
#define MANDELBROT_LEFT BUTTON_LEFT
#define MANDELBROT_RIGHT BUTTON_RIGHT
#define MANDELBROT_ZOOM_IN BUTTON_SCROLL_FWD
#define MANDELBROT_ZOOM_OUT BUTTON_SCROLL_BACK
#define MANDELBROT_MAXITER_INC (BUTTON_SELECT | BUTTON_RIGHT)
#define MANDELBROT_MAXITER_DEC (BUTTON_SELECT | BUTTON_LEFT)
#define MANDELBROT_RESET BUTTON_REC

#elif CONFIG_KEYPAD == SANSA_C200_PAD
#define MANDELBROT_QUIT BUTTON_POWER
#define MANDELBROT_UP BUTTON_UP
#define MANDELBROT_DOWN BUTTON_DOWN
#define MANDELBROT_LEFT BUTTON_LEFT
#define MANDELBROT_RIGHT BUTTON_RIGHT
#define MANDELBROT_ZOOM_IN BUTTON_VOL_UP
#define MANDELBROT_ZOOM_OUT BUTTON_VOL_DOWN
#define MANDELBROT_MAXITER_INC (BUTTON_SELECT | BUTTON_RIGHT)
#define MANDELBROT_MAXITER_DEC (BUTTON_SELECT | BUTTON_LEFT)
#define MANDELBROT_RESET BUTTON_REC

#elif CONFIG_KEYPAD == IRIVER_H10_PAD
#define MANDELBROT_QUIT          BUTTON_POWER
#define MANDELBROT_UP            BUTTON_SCROLL_UP
#define MANDELBROT_DOWN          BUTTON_SCROLL_DOWN
#define MANDELBROT_LEFT          BUTTON_LEFT
#define MANDELBROT_RIGHT         BUTTON_RIGHT
#define MANDELBROT_ZOOM_IN_PRE   BUTTON_PLAY
#define MANDELBROT_ZOOM_IN       (BUTTON_PLAY | BUTTON_REL)
#define MANDELBROT_ZOOM_OUT_PRE  BUTTON_PLAY
#define MANDELBROT_ZOOM_OUT      (BUTTON_PLAY | BUTTON_REPEAT)
#define MANDELBROT_MAXITER_INC   BUTTON_FF
#define MANDELBROT_MAXITER_DEC   BUTTON_REW
#define MANDELBROT_RESET         (BUTTON_PLAY | BUTTON_REW)

#elif CONFIG_KEYPAD == IRIVER_IFP7XX_PAD
#define MANDELBROT_QUIT          BUTTON_EQ
#define MANDELBROT_UP            BUTTON_UP
#define MANDELBROT_DOWN          BUTTON_DOWN
#define MANDELBROT_LEFT          BUTTON_LEFT
#define MANDELBROT_RIGHT         BUTTON_RIGHT
#define MANDELBROT_ZOOM_IN_PRE   BUTTON_SELECT
#define MANDELBROT_ZOOM_IN       (BUTTON_SELECT | BUTTON_REL)
#define MANDELBROT_ZOOM_OUT_PRE  BUTTON_SELECT
#define MANDELBROT_ZOOM_OUT      (BUTTON_SELECT | BUTTON_REPEAT)
#define MANDELBROT_MAXITER_INC   (BUTTON_PLAY | BUTTON_RIGHT)
#define MANDELBROT_MAXITER_DEC   (BUTTON_PLAY | BUTTON_LEFT)
#define MANDELBROT_RESET         BUTTON_MODE

#elif CONFIG_KEYPAD == GIGABEAT_S_PAD
#define MANDELBROT_QUIT          BUTTON_BACK
#define MANDELBROT_UP            BUTTON_UP
#define MANDELBROT_DOWN          BUTTON_DOWN
#define MANDELBROT_LEFT          BUTTON_LEFT
#define MANDELBROT_RIGHT         BUTTON_RIGHT
#define MANDELBROT_ZOOM_IN       BUTTON_VOL_UP
#define MANDELBROT_ZOOM_OUT      BUTTON_VOL_DOWN
#define MANDELBROT_MAXITER_INC   BUTTON_PREV
#define MANDELBROT_MAXITER_DEC   BUTTON_NEXT
#define MANDELBROT_RESET         BUTTON_MENU

#elif CONFIG_KEYPAD == MROBE100_PAD
#define MANDELBROT_QUIT         BUTTON_POWER
#define MANDELBROT_UP           BUTTON_UP
#define MANDELBROT_DOWN         BUTTON_DOWN
#define MANDELBROT_LEFT         BUTTON_LEFT
#define MANDELBROT_RIGHT        BUTTON_RIGHT
#define MANDELBROT_ZOOM_IN_PRE  BUTTON_SELECT
#define MANDELBROT_ZOOM_IN      (BUTTON_SELECT | BUTTON_REL)
#define MANDELBROT_ZOOM_OUT_PRE BUTTON_SELECT
#define MANDELBROT_ZOOM_OUT     (BUTTON_SELECT | BUTTON_REPEAT)
#define MANDELBROT_MAXITER_INC  BUTTON_MENU
#define MANDELBROT_MAXITER_DEC  BUTTON_PLAY
#define MANDELBROT_RESET        BUTTON_DISPLAY

#elif CONFIG_KEYPAD == IAUDIO_M3_PAD
#define MANDELBROT_QUIT            BUTTON_RC_REC
#define MANDELBROT_UP              BUTTON_RC_VOL_UP
#define MANDELBROT_DOWN            BUTTON_RC_VOL_DOWN
#define MANDELBROT_LEFT            BUTTON_RC_REW
#define MANDELBROT_RIGHT           BUTTON_RC_FF
#define MANDELBROT_ZOOM_IN_PRE     BUTTON_RC_PLAY
#define MANDELBROT_ZOOM_IN         (BUTTON_RC_PLAY | BUTTON_REL)
#define MANDELBROT_ZOOM_OUT_PRE    BUTTON_RC_PLAY
#define MANDELBROT_ZOOM_OUT        (BUTTON_RC_PLAY | BUTTON_REPEAT)
#define MANDELBROT_MAXITER_INC_PRE BUTTON_RC_MODE
#define MANDELBROT_MAXITER_INC     (BUTTON_RC_MODE|BUTTON_REL)
#define MANDELBROT_MAXITER_DEC_PRE BUTTON_RC_MODE
#define MANDELBROT_MAXITER_DEC     (BUTTON_RC_MODE|BUTTON_REPEAT)
#define MANDELBROT_RESET           BUTTON_RC_MENU

#elif CONFIG_KEYPAD == COWOND2_PAD
#define MANDELBROT_QUIT          BUTTON_POWER

#else
#error No keymap defined!
#endif

#ifdef HAVE_TOUCHSCREEN
#ifndef MANDELBROT_UP
#define MANDELBROT_UP            BUTTON_TOPMIDDLE
#endif
#ifndef MANDELBROT_DOWN
#define MANDELBROT_DOWN          BUTTON_BOTTOMMIDDLE
#endif
#ifndef MANDELBROT_LEFT
#define MANDELBROT_LEFT          BUTTON_MIDLEFT
#endif
#ifndef MANDELBROT_RIGHT
#define MANDELBROT_RIGHT         BUTTON_MIDRIGHT
#endif
#ifndef MANDELBROT_ZOOM_IN_PRE
#define MANDELBROT_ZOOM_IN_PRE   BUTTON_TOPRIGHT
#endif
#ifndef MANDELBROT_ZOOM_IN
#define MANDELBROT_ZOOM_IN      (BUTTON_TOPRIGHT | BUTTON_REL)
#endif
#ifndef MANDELBROT_ZOOM_OUT_PRE
#define MANDELBROT_ZOOM_OUT_PRE  BUTTON_TOPLEFT
#endif
#ifndef MANDELBROT_ZOOM_OUT
#define MANDELBROT_ZOOM_OUT     (BUTTON_TOPLEFT | BUTTON_REL)
#endif
#ifndef MANDELBROT_MAXITER_INC
#define MANDELBROT_MAXITER_INC   BUTTON_BOTTOMRIGHT
#endif
#ifndef MANDELBROT_MAXITER_DEC
#define MANDELBROT_MAXITER_DEC   BUTTON_BOTTOMLEFT
#endif
#ifndef MANDELBROT_RESET
#define MANDELBROT_RESET         BUTTON_CENTER
#endif
#endif

#if LCD_DEPTH < 8
#define USEGSLIB
#define MYLCD(fn) grey_ub_ ## fn
#define MYLCD_UPDATE()
#define MYXLCD(fn) grey_ub_ ## fn
#else
#define UPDATE_FREQ (HZ/50)
#define MYLCD(fn) rb->lcd_ ## fn
#define MYLCD_UPDATE() rb->lcd_update();
#define MYXLCD(fn) xlcd_ ## fn
#endif

static const struct plugin_api* rb;

/* Fixed point format s5.26: sign, 5 bits integer part, 26 bits fractional part */
static long x_min;
static long x_max;
static long x_step;
static long x_delta;
static long y_min;
static long y_max;
static long y_step;
static long y_delta;

static int px_min = 0;
static int px_max = LCD_WIDTH;
static int py_min = 0;
static int py_max = LCD_HEIGHT;

static int step_log2;
static unsigned max_iter;

#ifdef USEGSLIB
GREY_INFO_STRUCT
static unsigned char *gbuf;
static size_t  gbuf_size = 0;
static unsigned char imgbuffer[LCD_HEIGHT];
#else
static fb_data imgbuffer[LCD_HEIGHT];
#endif

/* 8 entries cyclical, last entry is black (convergence) */
#ifdef HAVE_LCD_COLOR
static const fb_data color[9] = {
    LCD_RGBPACK(255, 0, 159), LCD_RGBPACK(159, 0, 255), LCD_RGBPACK(0, 0, 255),
    LCD_RGBPACK(0, 159, 255), LCD_RGBPACK(0, 255, 128), LCD_RGBPACK(128, 255, 0),
    LCD_RGBPACK(255, 191, 0), LCD_RGBPACK(255, 0, 0),   LCD_RGBPACK(0, 0, 0)
};
#else /* greyscale */
static const fb_data color[9] = {
    255, 223, 191, 159, 128, 96, 64, 32, 0
};
#endif

#if CONFIG_CPU == SH7034

#define MULS16_ASR10(a, b) muls16_asr10(a, b)
static inline short muls16_asr10(short a, short b)
{
    short r;
    asm (
        "muls    %[a],%[b]   \n"
        "sts     macl,%[r]   \n"
        "shlr8   %[r]        \n"
        "shlr2   %[r]        \n"
        : /* outputs */
        [r]"=r"(r)
        : /* inputs */
        [a]"r"(a),
        [b]"r"(b)
    );
    return r;
}

#define MULS32_ASR26(a, b) muls32_asr26(a, b)
static inline long muls32_asr26(long a, long b)
{
    long r, t1, t2, t3;
    asm (
        /* Signed 32bit * 32bit -> 64bit multiplication.
           Notation: xxab * xxcd, where each letter represents 16 bits.
           xx is the 64 bit sign extension.  */
        "swap.w  %[a],%[t1]  \n" /* t1 = ba */
        "mulu    %[t1],%[b]  \n" /* a * d */
        "swap.w  %[b],%[t3]  \n" /* t3 = dc */
        "sts     macl,%[t2]  \n" /* t2 = a * d */
        "mulu    %[t1],%[t3] \n" /* a * c */
        "sts     macl,%[r]   \n" /* hi = a * c */
        "mulu    %[a],%[t3]  \n" /* b * c */
        "clrt                \n"
        "sts     macl,%[t3]  \n" /* t3 = b * c */
        "addc    %[t2],%[t3] \n" /* t3 += t2, carry -> t2 */
        "movt    %[t2]       \n"
        "mulu    %[a],%[b]   \n" /* b * d */
        "mov     %[t3],%[t1] \n" /* t2t3 <<= 16 */
        "xtrct   %[t2],%[t1] \n"
        "mov     %[t1],%[t2] \n"
        "shll16  %[t3]       \n"
        "sts     macl,%[t1]  \n" /* lo = b * d */
        "clrt                \n" /* hi.lo += t2t3 */
        "addc    %[t3],%[t1] \n"
        "addc    %[t2],%[r]  \n"
        "cmp/pz  %[a]        \n" /* ab >= 0 ? */
        "bt      1f          \n"
        "sub     %[b],%[r]   \n" /* no: hi -= cd (sign extension of ab is -1) */
    "1:                      \n"
        "cmp/pz  %[b]        \n" /* cd >= 0 ? */
        "bt      2f          \n"
        "sub     %[a],%[r]   \n" /* no: hi -= ab (sign extension of cd is -1) */
    "2:                      \n"
        /* Shift right by 26 and return low 32 bits */
        "shll2   %[r]        \n" /* hi <<= 6 */
        "shll2   %[r]        \n"
        "shll2   %[r]        \n"
        "shlr16  %[t1]       \n" /* (unsigned)lo >>= 26 */
        "shlr8   %[t1]       \n"
        "shlr2   %[t1]       \n"
        "or      %[t1],%[r]  \n" /* combine result */
        : /* outputs */
        [r] "=&r"(r),
        [t1]"=&r"(t1),
        [t2]"=&r"(t2),
        [t3]"=&r"(t3)
        : /* inputs */
        [a] "r"  (a),
        [b] "r"  (b)
    );
    return r;
}

#elif defined CPU_COLDFIRE

#define MULS16_ASR10(a, b) muls16_asr10(a, b)
static inline short muls16_asr10(short a, short b)
{
    asm (
        "muls.w  %[a],%[b]   \n"
        "asr.l   #8,%[b]     \n"
        "asr.l   #2,%[b]     \n"
        : /* outputs */
        [b]"+d"(b)
        : /* inputs */
        [a]"d" (a)
    );
    return b;
}

/* Needs the EMAC initialised to fractional mode w/o rounding and saturation */
#define MULS32_INIT() coldfire_set_macsr(EMAC_FRACTIONAL)
#define MULS32_ASR26(a, b) muls32_asr26(a, b)
static inline long muls32_asr26(long a, long b)
{
    long r, t1;
    asm (
        "mac.l   %[a], %[b], %%acc0  \n" /* multiply */
        "move.l  %%accext01, %[t1]   \n" /* get low part */
        "movclr.l %%acc0, %[r]       \n" /* get high part */
        "asl.l   #5, %[r]            \n" /* hi <<= 5, plus one free */
        "lsr.l   #3, %[t1]           \n" /* lo >>= 3 */
        "and.l   #0x1f, %[t1]        \n" /* mask out unrelated bits */
        "or.l    %[t1], %[r]         \n" /* combine result */
        : /* outputs */
        [r] "=d"(r),
        [t1]"=d"(t1)
        : /* inputs */
        [a] "d" (a),
        [b] "d" (b)
    );
    return r;
}

#elif defined CPU_ARM

#define MULS32_ASR26(a, b) muls32_asr26(a, b)
static inline long muls32_asr26(long a, long b)
{
    long r, t1;
    asm (
        "smull   %[r], %[t1], %[a], %[b]     \n"
        "mov     %[r], %[r], lsr #26         \n"
        "orr     %[r], %[r], %[t1], lsl #6   \n"
        : /* outputs */
        [r] "=&r,&r,&r"(r),
        [t1]"=&r,&r,&r"(t1)
        : /* inputs */
        [a] "%r,%r,%r" (a),
        [b] "r,0,1" (b)
    );
    return r;
}

#endif /* CPU */

/* default macros */
#ifndef MULS16_ASR10
#define MULS16_ASR10(a, b) ((short)(((long)(a) * (long)(b)) >> 10))
#endif
#ifndef MULS32_ASR26
#define MULS32_ASR26(a, b) ((long)(((long long)(a) * (long long)(b)) >> 26))
#endif
#ifndef MULS32_INIT
#define MULS32_INIT()
#endif

int ilog2_fp(long value) /* calculate integer log2(value_fp_6.26) */
{
    int i = 0;

    if (value <= 0) {
        return -32767;
    } else if (value > (1L<<26)) {
        while (value >= (2L<<26)) {
            value >>= 1;
            i++;
        }
    } else {
        while (value < (1L<<26)) {
            value <<= 1;
            i--;
        }
    }
    return i;
}

void recalc_parameters(void)
{
    x_step = (x_max - x_min) / LCD_WIDTH;
    x_delta = (x_step * LCD_WIDTH) / 8;
    y_step = (y_max - y_min) / LCD_HEIGHT;
    y_delta = (y_step * LCD_HEIGHT) / 8;
    step_log2 = ilog2_fp(MIN(x_step, y_step));
    max_iter = MAX(15, -15 * step_log2 - 45);
}

#if CONFIG_LCD == LCD_SSD1815 
/* Recorder, Ondio: pixel_height == 1.25 * pixel_width */
#define MB_HEIGHT (LCD_HEIGHT*5/4)
#else
/* square pixels */
#define MB_HEIGHT LCD_HEIGHT
#endif

#define MB_XOFS (-0x03000000L)       /* -0.75 (s5.26) */
#if 3000*MB_HEIGHT/LCD_WIDTH >= 2400 /* width is limiting factor */
#define MB_XFAC (0x06000000LL)       /* 1.5 (s5.26) */
#define MB_YFAC (MB_XFAC*MB_HEIGHT/LCD_WIDTH)
#else                                /* height is limiting factor */
#define MB_YFAC (0x04cccccdLL)       /* 1.2 (s5.26) */
#define MB_XFAC (MB_YFAC*LCD_WIDTH/MB_HEIGHT)
#endif

void init_mandelbrot_set(void)
{
    x_min = MB_XOFS-MB_XFAC;
    x_max = MB_XOFS+MB_XFAC;
    y_min = -MB_YFAC;
    y_max = MB_YFAC;
    recalc_parameters();
}

void calc_mandelbrot_low_prec(void)
{
    long start_tick, last_yield;
#ifndef USEGSLIB
    long next_update = *rb->current_tick;
    int last_px = px_min;
#endif
    unsigned n_iter;
    long a32, b32;
    short x, x2, y, y2, a, b;
    int p_x, p_y;

    start_tick = last_yield = *rb->current_tick;
    
    for (p_x = 0, a32 = x_min; p_x < px_max; p_x++, a32 += x_step) {
        if (p_x < px_min)
            continue;
        a = a32 >> 16;
        for (p_y = LCD_HEIGHT-1, b32 = y_min; p_y >= py_min; p_y--, b32 += y_step) {
            if (p_y >= py_max)
                continue;
            b = b32 >> 16;
            x = a;
            y = b;
            n_iter = 0;

            while (++n_iter <= max_iter) {
                x2 = MULS16_ASR10(x, x);
                y2 = MULS16_ASR10(y, y);

                if (x2 + y2 > (4<<10)) break;

                y = 2 * MULS16_ASR10(x, y) + b;
                x = x2 - y2 + a;
            }

            if  (n_iter > max_iter)
                imgbuffer[p_y] = color[8];
            else
                imgbuffer[p_y] = color[n_iter & 7];

            /* be nice to other threads:
             * if at least one tick has passed, yield */
            if  (*rb->current_tick > last_yield) {
                rb->yield();
                last_yield = *rb->current_tick;
            }
        }
#ifdef USEGSLIB
        grey_ub_gray_bitmap_part(imgbuffer, 0, py_min, 1,
                                 p_x, py_min, 1, py_max - py_min);
#else
        rb->lcd_bitmap_part(imgbuffer, 0, py_min, 1,
                            p_x, py_min, 1, py_max - py_min);
        if ((p_x == px_max - 1) || TIME_AFTER(*rb->current_tick, next_update))
        {
            next_update = *rb->current_tick + UPDATE_FREQ;
            rb->lcd_update_rect(last_px, py_min, p_x - last_px + 1,
                                py_max - py_min);
            last_px = p_x;
        }
#endif
    }
}

void calc_mandelbrot_high_prec(void)
{
    long start_tick, last_yield;
#ifndef USEGSLIB
    long next_update = *rb->current_tick;
    int last_px = px_min;
#endif
    unsigned n_iter;
    long x, x2, y, y2, a, b;
    int p_x, p_y;

    MULS32_INIT();
    start_tick = last_yield = *rb->current_tick;

    for (p_x = 0, a = x_min; p_x < px_max; p_x++, a += x_step) {
        if (p_x < px_min)
            continue;
        for (p_y = LCD_HEIGHT-1, b = y_min; p_y >= py_min; p_y--, b += y_step) {
            if (p_y >= py_max)
                continue;
            x = a;
            y = b;
            n_iter = 0;

            while (++n_iter <= max_iter) {
                x2 = MULS32_ASR26(x, x);
                y2 = MULS32_ASR26(y, y);

                if (x2 + y2 > (4L<<26)) break;

                y = 2 * MULS32_ASR26(x, y) + b;
                x = x2 - y2 + a;
            }

            if  (n_iter > max_iter)
                imgbuffer[p_y] = color[8];
            else
                imgbuffer[p_y] = color[n_iter & 7];

            /* be nice to other threads:
             * if at least one tick has passed, yield */
            if  (*rb->current_tick > last_yield) {
                rb->yield();
                last_yield = *rb->current_tick;
            }
        }
#ifdef USEGSLIB
        grey_ub_gray_bitmap_part(imgbuffer, 0, py_min, 1,
                                 p_x, py_min, 1, py_max - py_min);
#else
        rb->lcd_bitmap_part(imgbuffer, 0, py_min, 1,
                            p_x, py_min, 1, py_max-py_min);
        if ((p_x == px_max - 1) || TIME_AFTER(*rb->current_tick, next_update))
        {
            next_update = *rb->current_tick + UPDATE_FREQ;
            rb->lcd_update_rect(last_px, py_min, p_x - last_px + 1,
                                py_max - py_min);
            last_px = p_x;
        }
#endif
    }
}

void cleanup(void *parameter)
{
    (void)parameter;
#ifdef USEGSLIB
    grey_release();
#endif
}

#define REDRAW_NONE    0
#define REDRAW_PARTIAL 1
#define REDRAW_FULL    2

enum plugin_status plugin_start(const struct plugin_api* api, const void* parameter)
{
    int button;
    int lastbutton = BUTTON_NONE;
    int redraw = REDRAW_FULL;

    rb = api;
    (void)parameter;

#ifdef USEGSLIB
    /* get the remainder of the plugin buffer */
    gbuf = (unsigned char *) rb->plugin_get_buffer(&gbuf_size);

    /* initialize the greyscale buffer.*/
    if (!grey_init(rb, gbuf, gbuf_size, GREY_ON_COP,
                   LCD_WIDTH, LCD_HEIGHT, NULL))
    {
        rb->splash(HZ, "Couldn't init greyscale display");
        return 0;
    }
    grey_show(true); /* switch on greyscale overlay */
#else
    xlcd_init(rb);
#endif

#if LCD_DEPTH > 1
    rb->lcd_set_backdrop(NULL);
#endif

    init_mandelbrot_set();

    /* main loop */
    while (true) {
        if (redraw > REDRAW_NONE) {
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
            rb->cpu_boost(true);
#endif
            if (redraw == REDRAW_FULL) {
                MYLCD(clear_display)();
                MYLCD_UPDATE();
            }

            if (step_log2 <= -10) /* select precision */
                calc_mandelbrot_high_prec();
            else
                calc_mandelbrot_low_prec();

#ifdef HAVE_ADJUSTABLE_CPU_FREQ
            rb->cpu_boost(false);
#endif
            px_min = 0;
            px_max = LCD_WIDTH;
            py_min = 0;
            py_max = LCD_HEIGHT;
            redraw = REDRAW_NONE;
        }

        button = rb->button_get(true);
        switch (button) {
#ifdef MANDELBROT_RC_QUIT
        case MANDELBROT_RC_QUIT:
#endif
        case MANDELBROT_QUIT:
#ifdef USEGSLIB
            grey_release();
#endif
            return PLUGIN_OK;

        case MANDELBROT_ZOOM_OUT:
#ifdef MANDELBROT_ZOOM_OUT_PRE
            if (lastbutton != MANDELBROT_ZOOM_OUT_PRE)
                break;
#endif
            x_min -= x_delta;
            x_max += x_delta;
            y_min -= y_delta;
            y_max += y_delta;
            recalc_parameters();
            redraw = REDRAW_FULL;
            break;


        case MANDELBROT_ZOOM_IN:
#ifdef MANDELBROT_ZOOM_IN_PRE
            if (lastbutton != MANDELBROT_ZOOM_IN_PRE)
                break;
#endif
#ifdef MANDELBROT_ZOOM_IN2
        case MANDELBROT_ZOOM_IN2:
#endif
            x_min += x_delta;
            x_max -= x_delta;
            y_min += y_delta;
            y_max -= y_delta;
            recalc_parameters();
            redraw = REDRAW_FULL;
            break;

        case MANDELBROT_UP:
            y_min += y_delta;
            y_max += y_delta;
            MYXLCD(scroll_down)(LCD_HEIGHT/8);
            MYLCD_UPDATE();
            py_max = (LCD_HEIGHT/8);
            redraw = REDRAW_PARTIAL;
            break;

        case MANDELBROT_DOWN:
            y_min -= y_delta;
            y_max -= y_delta;
            MYXLCD(scroll_up)(LCD_HEIGHT/8);
            MYLCD_UPDATE();
            py_min = (LCD_HEIGHT-LCD_HEIGHT/8);
            redraw = REDRAW_PARTIAL;
            break;

        case MANDELBROT_LEFT:
            x_min -= x_delta;
            x_max -= x_delta;
            MYXLCD(scroll_right)(LCD_WIDTH/8);
            MYLCD_UPDATE();
            px_max = (LCD_WIDTH/8);
            redraw = REDRAW_PARTIAL;
            break;

        case MANDELBROT_RIGHT:
            x_min += x_delta;
            x_max += x_delta;
            MYXLCD(scroll_left)(LCD_WIDTH/8);
            MYLCD_UPDATE();
            px_min = (LCD_WIDTH-LCD_WIDTH/8);
            redraw = REDRAW_PARTIAL;
            break;

        case MANDELBROT_MAXITER_DEC:
#ifdef MANDELBROT_MAXITER_DEC_PRE
            if (lastbutton != MANDELBROT_MAXITER_DEC_PRE)
                break;
#endif
            if (max_iter >= 15) {
                max_iter -= max_iter / 3;
                redraw = REDRAW_FULL;
            }
            break;

        case MANDELBROT_MAXITER_INC:
#ifdef MANDELBROT_MAXITER_INC_PRE
            if (lastbutton != MANDELBROT_MAXITER_INC_PRE)
                break;
#endif
            max_iter += max_iter / 2;
            redraw = REDRAW_FULL;
            break;

        case MANDELBROT_RESET:
            init_mandelbrot_set();
            redraw = REDRAW_FULL;
            break;

        default:
            if (rb->default_event_handler_ex(button, cleanup, NULL)
                == SYS_USB_CONNECTED)
                return PLUGIN_USB_CONNECTED;
            break;
        }
        if (button != BUTTON_NONE)
            lastbutton = button;
    }
#ifdef USEGSLIB
    grey_release();
#endif
    return PLUGIN_OK;
}
#endif
