/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2006 Albert Veli
 *
 * 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.
 *
 ****************************************************************************/

/* Improvised creds goes to:
 *
 * - Anders Clausen for ingeniously inventing the name Invadrox.
 * - Linus Nielsen-Feltzing for patiently answering n00b questions.
 */

#include "plugin.h"
#include "lib/highscore.h"
#include "lib/helper.h"

/* bitmaps */
#include "pluginbitmaps/invadrox_background.h"

/* get dimensions for later use from the bitmaps */
#include "pluginbitmaps/invadrox_aliens.h"
#include "pluginbitmaps/invadrox_ships.h"
#include "pluginbitmaps/invadrox_bombs.h"
#include "pluginbitmaps/invadrox_alien_explode.h"
#include "pluginbitmaps/invadrox_shield.h"
#include "pluginbitmaps/invadrox_ufo.h"
#include "pluginbitmaps/invadrox_ufo_explode.h"
#include "pluginbitmaps/invadrox_numbers.h"
#include "pluginbitmaps/invadrox_fire.h"
#define ALIEN_WIDTH           (BMPWIDTH_invadrox_aliens/2)
#define ALIEN_HEIGHT          (BMPHEIGHT_invadrox_aliens/3)
#define SHIP_WIDTH            BMPWIDTH_invadrox_ships
#define SHIP_HEIGHT           (BMPHEIGHT_invadrox_ships/3)
#define BOMB_WIDTH            (BMPWIDTH_invadrox_bombs/3)
#define BOMB_HEIGHT           (BMPHEIGHT_invadrox_bombs/6)
#define ALIEN_EXPLODE_WIDTH   BMPWIDTH_invadrox_alien_explode
#define ALIEN_EXPLODE_HEIGHT  BMPHEIGHT_invadrox_alien_explode
#define SHIELD_WIDTH          BMPWIDTH_invadrox_shield
#define SHIELD_HEIGHT         BMPHEIGHT_invadrox_shield
#define UFO_WIDTH             BMPWIDTH_invadrox_ufo
#define UFO_HEIGHT            BMPHEIGHT_invadrox_ufo
#define UFO_EXPLODE_WIDTH     BMPWIDTH_invadrox_ufo_explode
#define UFO_EXPLODE_HEIGHT    BMPHEIGHT_invadrox_ufo_explode
#define NUMBERS_WIDTH         (BMPWIDTH_invadrox_numbers/10)
#define FONT_HEIGHT           BMPHEIGHT_invadrox_numbers
#define FIRE_WIDTH            BMPWIDTH_invadrox_fire
#define FIRE_HEIGHT           BMPHEIGHT_invadrox_fire

PLUGIN_HEADER

/* Original graphics is only 1bpp so it should be portable
 * to most targets. But for now, only support the simple ones.
 */
#ifndef HAVE_LCD_BITMAP
    #error INVADROX: Unsupported LCD
#endif

#if (LCD_DEPTH < 2)
    #error INVADROX: Unsupported LCD
#endif

/* #define DEBUG */
#ifdef DEBUG
#define DBG(format, arg...) { DEBUGF("%s: " format, __FUNCTION__, ## arg); }
#else
#define DBG(format, arg...) {}
#endif

#ifndef ABS
#define ABS(a) (((a) < 0) ? -(a) : (a))
#endif

#if CONFIG_KEYPAD == IRIVER_H100_PAD

#define QUIT BUTTON_OFF
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define FIRE BUTTON_ON

#elif CONFIG_KEYPAD == IRIVER_H300_PAD

#define QUIT BUTTON_OFF
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define FIRE BUTTON_SELECT

#elif (CONFIG_KEYPAD == IRIVER_H10_PAD)

#define QUIT BUTTON_POWER
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define FIRE BUTTON_PLAY

#elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
      (CONFIG_KEYPAD == IPOD_3G_PAD) || \
      (CONFIG_KEYPAD == IPOD_1G2G_PAD)

#define QUIT BUTTON_MENU
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define FIRE BUTTON_SELECT

#elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD

#define QUIT BUTTON_POWER
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define FIRE BUTTON_SELECT

#elif CONFIG_KEYPAD == GIGABEAT_PAD

#define QUIT BUTTON_POWER
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define FIRE BUTTON_SELECT

#elif CONFIG_KEYPAD == SANSA_E200_PAD

#define QUIT BUTTON_POWER
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define FIRE BUTTON_SELECT

#elif CONFIG_KEYPAD == SANSA_FUZE_PAD

#define QUIT (BUTTON_HOME|BUTTON_REPEAT)
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define FIRE BUTTON_SELECT

#elif CONFIG_KEYPAD == TATUNG_TPJ1022_PAD

/* TODO: Figure out which buttons to use for Tatung Elio TPJ-1022 */
#define QUIT BUTTON_AB
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define FIRE BUTTON_MENU

#elif CONFIG_KEYPAD == GIGABEAT_S_PAD

#define QUIT BUTTON_BACK
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define FIRE BUTTON_SELECT

#elif CONFIG_KEYPAD == COWON_D2_PAD

#define QUIT BUTTON_POWER
#define LEFT BUTTON_MINUS
#define RIGHT BUTTON_PLUS
#define FIRE BUTTON_MENU

#elif CONFIG_KEYPAD == IAUDIO67_PAD

#define QUIT BUTTON_POWER
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define FIRE BUTTON_PLAY

#elif CONFIG_KEYPAD == CREATIVEZVM_PAD

#define QUIT BUTTON_BACK
#define LEFT BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define FIRE BUTTON_SELECT

#elif CONFIG_KEYPAD == PHILIPS_SA9200_PAD

#define QUIT BUTTON_POWER
#define LEFT BUTTON_PREV
#define RIGHT BUTTON_NEXT
#define FIRE BUTTON_PLAY

#elif CONFIG_KEYPAD == ONDAVX747_PAD || \
CONFIG_KEYPAD == ONDAVX777_PAD || \
CONFIG_KEYPAD == MROBE500_PAD

#define QUIT BUTTON_POWER

#elif CONFIG_KEYPAD == SAMSUNG_YH_PAD

#define QUIT  BUTTON_REC
#define LEFT  BUTTON_LEFT
#define RIGHT BUTTON_RIGHT
#define FIRE  BUTTON_PLAY

#elif CONFIG_KEYPAD == PBELL_VIBE500_PAD

#define QUIT  BUTTON_REC
#define LEFT  BUTTON_PREV
#define RIGHT BUTTON_NEXT
#define FIRE  BUTTON_OK

#else
    #error INVADROX: Unsupported keypad
#endif

#ifndef RC_QUIT
#define RC_QUIT 0
#endif

#ifdef HAVE_TOUCHSCREEN

#ifndef QUIT
#define QUIT 0
#endif
#ifndef LEFT
#define LEFT 0
#endif
#ifndef RIGHT
#define RIGHT 0
#endif
#ifndef FIRE
#define FIRE 0
#endif

#define TOUCHSCREEN_QUIT    BUTTON_TOPLEFT
#define TOUCHSCREEN_LEFT    (BUTTON_MIDLEFT | BUTTON_BOTTOMLEFT)
#define TOUCHSCREEN_RIGHT   (BUTTON_MIDRIGHT | BUTTON_BOTTOMRIGHT)
#define TOUCHSCREEN_FIRE    (BUTTON_CENTER | BUTTON_BOTTOMMIDDLE)

#define ACTION_QUIT     (QUIT | TOUCHSCREEN_QUIT | RC_QUIT)
#define ACTION_LEFT     (LEFT | TOUCHSCREEN_LEFT)
#define ACTION_RIGHT    (RIGHT | TOUCHSCREEN_RIGHT)
#define ACTION_FIRE     (FIRE | TOUCHSCREEN_FIRE)

#else /* HAVE_TOUCHSCREEN */

#define ACTION_QUIT  (QUIT | RC_QUIT)
#define ACTION_LEFT  LEFT
#define ACTION_RIGHT RIGHT
#define ACTION_FIRE  FIRE

#endif

#ifndef UNUSED
#define UNUSED __attribute__ ((unused))
#endif

/* Defines common to all models */
#define UFO_Y (SCORENUM_Y + FONT_HEIGHT + ALIEN_HEIGHT)
#define PLAYFIELD_Y (LCD_HEIGHT - SHIP_HEIGHT - 2)
#define PLAYFIELD_WIDTH (LCD_WIDTH - 2 * PLAYFIELD_X)
#define LEVEL_X (LCD_WIDTH - PLAYFIELD_X - LIVES_X - 2 * NUMBERS_WIDTH - 3 * NUM_SPACING)
#define SHIP_MIN_X (PLAYFIELD_X + PLAYFIELD_WIDTH / 5 - SHIELD_WIDTH / 2 - SHIP_WIDTH)
#define SHIP_MAX_X (PLAYFIELD_X + 4 * PLAYFIELD_WIDTH / 5 + SHIELD_WIDTH / 2)
/* SCORE_Y = 0 for most targets. Gigabeat redefines it later. */
#define SCORE_Y 0
#define MAX_LIVES 8


/* m:robe 500 defines */
#if ((LCD_WIDTH == 640) && (LCD_HEIGHT == 480)) || \
    ((LCD_WIDTH == 480) && (LCD_HEIGHT == 640))

/* Original arcade game size 224x240, 1bpp with
 * red overlay at top and green overlay at bottom.
 *
 * M:Robe 500: 640x480x16
 * ======================
 */

#define ARCADISH_GRAPHICS
#define PLAYFIELD_X 48
#define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
#define ALIEN_START_Y (UFO_Y + ALIEN_HEIGHT)
#define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
#define SCORENUM_Y (SCORE_Y + FONT_HEIGHT + 2)
#define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
#define SHIELD_Y (PLAYFIELD_Y - 5 * SHIP_HEIGHT)
#define LIVES_X 10
#define MAX_Y 18

/* iPod Video defines */
#elif (LCD_WIDTH == 320) && (LCD_HEIGHT == 240)

/* Original arcade game size 224x240, 1bpp with
 * red overlay at top and green overlay at bottom.
 *
 * iPod Video: 320x240x16
 * ======================
 * X: 48p padding at left/right gives 224p playfield in middle.
 *    10p "border" gives 204p actual playfield. UFO use full 224p.
 * Y: Use full 240p.
 *
 * MAX_X = (204 - 12) / 2 - 1 = 95
 *
 * Y: Score text 7       0
 *    Space      10      7
 *    Score      7       17
 *    Space      8       24
 * 3  Ufo        7       32
 * 2  Space      Aliens start at 32 + 3 * 8 = 56
 * 0  aliens     9*8     56  -
 *    space     ~7*8     128  | 18.75 aliens space between
 *    shield     2*8     182  | first alien and ship.
 *    space      8       198  | MAX_Y = 18
 *    ship       8       206 -
 *    space      2*8     214
 *    hline      1       230 - PLAYFIELD_Y
 *    bottom border 10   240
 *    Lives and Level goes inside bottom border
 */

#define ARCADISH_GRAPHICS
#define PLAYFIELD_X 48
#define SHIP_Y (PLAYFIELD_Y - 3 * SHIP_HEIGHT)
#define ALIEN_START_Y (UFO_Y + 3 * ALIEN_HEIGHT)
#define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
#define SCORENUM_Y SCORE_Y + (2 * (FONT_HEIGHT + 1) + 1)
#define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
#define SHIELD_Y (PLAYFIELD_Y - 6 * SHIP_HEIGHT)
#define LIVES_X 10
#define MAX_Y 18

#elif (LCD_WIDTH == 176) && (LCD_HEIGHT == 220)

/* Sandisk Sansa e200: 176x220x16
 * ==============================
 * X: No padding. 8p border -> 160p playfield.
 *
 * 8p Aliens with 3p spacing -> 88 + 30 = 118p aliens block.
 * (160 - 118) / 2 = 21 rounds for whole block (more than original)
 * MAX_X = (160 - 8) / 2 - 1 = 75 rounds for single alien (less than original)
 *
 *    LOGO       70       0
 *    Score text 5        70
 *    Space      5        75
 * Y  Score      5        80
 *    Space      10       85
 * 2  Ufo        5        95
 * 2  Space      10      100
 * 0  aliens     9*5     110 -
 *    space     ~7*5     155  | 18.6 aliens space between
 *    shield     2*5     188  | first alien and ship.
 *    space      5       198  | MAX_Y = 18
 *    ship       5       203 -
 *    space      5       208
 *    hline      1       213 PLAYFIELD_Y
 *    bottom border 6
 *    LCD_HEIGHT         220
 *    Lives and Level goes inside bottom border
 */

#define SMALL_GRAPHICS
#define PLAYFIELD_X 0
#define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
#define SHIELD_Y (SHIP_Y - SHIP_HEIGHT - SHIELD_HEIGHT)
#define ALIEN_START_Y (UFO_Y + 3 * SHIP_HEIGHT)
/* Redefine SCORE_Y */
#undef SCORE_Y
#define SCORE_Y 70
#define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
#define SCORENUM_Y (SCORE_Y + 2 * FONT_HEIGHT)
#define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
#define LIVES_X 8
#define MAX_Y 18


#elif (LCD_WIDTH == 176) && (LCD_HEIGHT == 132)

/* iPod Nano: 176x132x16
 * ======================
 * X: No padding. 8p border -> 160p playfield.
 *
 *    LIVES_X 8
 *    ALIEN_WIDTH 8
 *    ALIEN_HEIGHT 5
 *    ALIEN_SPACING 3
 *    SHIP_WIDTH 10
 *    SHIP_HEIGHT 5
 *    FONT_HEIGHT 5
 *    UFO_WIDTH 10
 *    UFO_HEIGHT 5
 *    SHIELD_WIDTH 15
 *    SHIELD_HEIGHT 10
 *    MAX_X 75
 *    MAX_Y = 18
 *    ALIEN_START_Y (UFO_Y + 12)
 *
 * 8p Aliens with 3p spacing -> 88 + 30 = 118p aliens block.
 * (160 - 118) / 2 = 21 rounds for whole block (more than original)
 * MAX_X = (160 - 8) / 2 - 1 = 75 rounds for single alien (less than original)
 *
 * Y: Scoreline  5         0 (combine scoretext and numbers on same line)
 *    Space      5         5
 * 1  Ufo        5        10
 * 3  Space      7        15
 * 2  aliens     9*5      22 -
 *    space     ~7*5      67  | Just above 18 aliens space between
 *    shield     2*5     100  | first alien and ship.
 *    space      5       110  | MAX_Y = 18
 *    ship       5       115 -
 *    space      5       120
 *    hline      1       125 PLAYFIELD_Y
 *    bottom border 6    126
 *    LCD_HEIGHT         131
 *    Lives and Level goes inside bottom border
 */

#define SMALL_GRAPHICS
#define PLAYFIELD_X 0
#define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
#define ALIEN_START_Y (UFO_Y + 12)
#define SCORENUM_X (PLAYFIELD_X + 6 * NUMBERS_WIDTH + 5 * NUM_SPACING)
#define SCORENUM_Y SCORE_Y
#define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 4 * NUMBERS_WIDTH - 3 * NUM_SPACING)
#define SHIELD_Y (SHIP_Y - SHIP_HEIGHT - SHIELD_HEIGHT)
#define LIVES_X 8
#define MAX_Y 18

#elif (LCD_WIDTH == 160) && (LCD_HEIGHT == 128)

/* iAudio X5, iRiver H10 20Gb, iPod 3g/4g, H100, M5: 160x128
 * =========================================================
 * X: No padding. No border -> 160p playfield.
 *
 *    LIVES_X 0
 *    ALIEN_WIDTH 8
 *    ALIEN_HEIGHT 5
 *    ALIEN_SPACING 3
 *    SHIP_WIDTH 10
 *    SHIP_HEIGHT 5
 *    FONT_HEIGHT 5
 *    UFO_WIDTH 10
 *    UFO_HEIGHT 5
 *    SHIELD_WIDTH 15
 *    SHIELD_HEIGHT 10
 *    MAX_X 75
 *    MAX_Y = 18
 *    ALIEN_START_Y (UFO_Y + 10)
 *
 * 8p Aliens with 3p spacing -> 88 + 30 = 118p aliens block.
 * (160 - 118) / 2 = 21 rounds for whole block (more than original)
 * MAX_X = (160 - 8) / 2 - 1 = 75 rounds for single alien (less than original)
 *
 * Y: Scoreline  5         0 (combine scoretext and numbers on same line)
 *    Space      5         5
 * 1  Ufo        5        10
 * 2  Space      5        15
 * 8  aliens     9*5      20 -
 *    space     ~6*5      65  | Just above 18 aliens space between
 *    shield     2*5      96  | first alien and ship.
 *    space      5       106  | MAX_Y = 18
 *    ship       5       111 -
 *    space      5       116
 *    hline      1       121 PLAYFIELD_Y
 *    bottom border 6    122
 *    LCD_HEIGHT         128
 *    Lives and Level goes inside bottom border
 */

#define SMALL_GRAPHICS
#define PLAYFIELD_X 0
#define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
#define ALIEN_START_Y (UFO_Y + 10)
#define SCORENUM_X (PLAYFIELD_X + 6 * NUMBERS_WIDTH + 5 * NUM_SPACING)
#define SCORENUM_Y SCORE_Y
#define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 4 * NUMBERS_WIDTH - 3 * NUM_SPACING)
#define SHIELD_Y (SHIP_Y - SHIP_HEIGHT - SHIELD_HEIGHT)
#define LIVES_X 0
#define MAX_Y 18


#elif (LCD_WIDTH == 240) && ((LCD_HEIGHT == 320) || (LCD_HEIGHT == 400))

/* Gigabeat: 240x320x16
 * ======================
 * X: 8p padding at left/right gives 224p playfield in middle.
 *    10p "border" gives 204p actual playfield. UFO use full 224p.
 * Y: Use bottom 240p for playfield and top 80 pixels for logo.
 *
 * MAX_X = (204 - 12) / 2 - 1 = 95
 *
 * Y: Score text 7       0   + 80
 *    Space      10      7   + 80
 *    Score      7       17  + 80
 *    Space      8       24  + 80
 * 3  Ufo        7       32  + 80
 * 2  Space      Aliens start at 32 + 3 * 8 = 56
 * 0  aliens     9*8     56  -
 *    space     ~7*8     128  | 18.75 aliens space between
 *    shield     2*8     182  | first alien and ship.
 *    space      8       198  | MAX_Y = 18
 *    ship       8       206 -
 *    space      2*8     214
 *    hline      1       230 310  - PLAYFIELD_Y
 *    bottom border 10   240 320
 *    Lives and Level goes inside bottom border
 */

#define ARCADISH_GRAPHICS
#define PLAYFIELD_X 8
#define SHIP_Y (PLAYFIELD_Y - 3 * SHIP_HEIGHT)
#define ALIEN_START_Y (UFO_Y + 3 * ALIEN_HEIGHT)
/* Redefine SCORE_Y */
#undef SCORE_Y
#define SCORE_Y 80
#define SCORENUM_X (PLAYFIELD_X + NUMBERS_WIDTH)
#define SCORENUM_Y SCORE_Y + (2 * (FONT_HEIGHT + 1) + 1)
#define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 1 - 6 * NUMBERS_WIDTH - 5 * NUM_SPACING)
#define SHIELD_Y (PLAYFIELD_Y - 6 * SHIP_HEIGHT)
#define LIVES_X 10
#define MAX_Y 18

#elif (LCD_WIDTH == 220) && (LCD_HEIGHT == 176)

/* TPJ1022, H300, iPod Color: 220x176x16
 * ============================
 * X: 0p padding at left/right gives 220p playfield in middle.
 *    8p "border" gives 204p actual playfield. UFO use full 220p.
 * Y: Use full 176p for playfield.
 *
 * MAX_X = (204 - 12) / 2 - 1 = 95
 *
 * Y: Score text 7       0
 *    Space      8       7
 * 1  Ufo        7       15
 * 7  Space      Aliens start at 15 + 3 * 8 = 56
 * 6  aliens     9*8     25  -
 *    space     ~7*8     103  | 15.6 aliens space between
 *    shield     2*8     126  | first alien and ship.
 *    space      8       142  | MAX_Y = 15
 *    ship       8       150 -
 *    space      8       158
 *    hline      1       166 - PLAYFIELD_Y
 *    bottom border 10   176
 *    Lives and Level goes inside bottom border
 */

#define ARCADISH_GRAPHICS
#define PLAYFIELD_X 0
#define SHIP_Y (PLAYFIELD_Y - 2 * SHIP_HEIGHT)
#define ALIEN_START_Y (UFO_Y + 10)
#define SCORENUM_Y SCORE_Y
#define SCORENUM_X (PLAYFIELD_X + 6 * NUMBERS_WIDTH + 6 * NUM_SPACING)
#define HISCORENUM_X (LCD_WIDTH - PLAYFIELD_X - 4 * NUMBERS_WIDTH - 3 * NUM_SPACING)
#define SHIELD_Y (PLAYFIELD_Y - 5 * SHIP_HEIGHT)
#define LIVES_X 8
#define MAX_Y 15


#else
    #error INVADROX: Unsupported LCD type
#endif

#define MAX_X ((LCD_WIDTH-LIVES_X*2-PLAYFIELD_X*2 - ALIEN_WIDTH)/2 - 1)

/* Defines common to each "graphic type" */
#ifdef ARCADISH_GRAPHICS

#define SHOT_HEIGHT 5
#define ALIEN_SPACING 4
#define ALIEN_SPEED 2
#define UFO_SPEED 1
#define NUM_SPACING 3
#define FIRE_SPEED 8
#define BOMB_SPEED 3
#define ALIENS 11

#elif defined SMALL_GRAPHICS

#define SHOT_HEIGHT 4
#define ALIEN_SPACING 3
#define ALIEN_SPEED 2
#define UFO_SPEED 1
#define NUM_SPACING 2
#define FIRE_SPEED 6
#define BOMB_SPEED 2
#define ALIENS 11

#else
    #error Graphic type not defined
#endif


/* Colors */
#if (LCD_DEPTH >= 8)
#define SLIME_GREEN LCD_RGBPACK(31, 254, 31)
#define UFO_RED LCD_RGBPACK(254, 31, 31)
#elif (LCD_DEPTH == 2)
#define SLIME_GREEN LCD_LIGHTGRAY
#define UFO_RED LCD_LIGHTGRAY
#else
#error LCD type not implemented yet
#endif

/* Alien states */
#define DEAD 0
#define ALIVE 1
#define BOMBER 2

/* Fire/bomb/ufo states */
#define S_IDLE 0
#define S_ACTIVE 1
#define S_SHOWSCORE 2
#define S_EXPLODE -9

/* Fire/bomb targets */
#define TARGET_TOP 0
#define TARGET_SHIELD 1
#define TARGET_SHIP 2
#define TARGET_BOTTOM 3
#define TARGET_UFO 4

#define HISCOREFILE PLUGIN_GAMES_DIR "/invadrox.high"


/* The time (in ms) for one iteration through the game loop - decrease this
 * to speed up the game - note that current_tick is (currently) only accurate
 * to 10ms.
 */
#define CYCLETIME 40


/* Physical x is at PLAYFIELD_X + LIVES_X + x * ALIEN_SPEED
 * Physical y is at y * ALIEN_HEIGHT
 */
struct alien {
    int x;     /* x-coordinate (0 - 95) */
    int y;     /* y-coordinate (0 - 18) */
    unsigned char type;  /* 0 (Kang), 1 (Kodos), 2 (Serak) */
    unsigned char state; /* Dead, alive or bomber */
};

/* Aliens box 5 rows * ALIENS aliens in each row */
struct alien aliens[5 * ALIENS];

#define MAX_BOMBS 4
struct bomb {
    int x, y;
    unsigned char type;
    unsigned char frame; /* Current animation frame */
    unsigned char frames; /* Number of frames in animation */
    unsigned char target; /* Remember target during explosion frames */
    int state; /* 0 (IDLE) = inactive, 1 (FIRE) or negative, exploding */
};
struct bomb bombs[MAX_BOMBS];
/* Increase max_bombs at higher levels */
int max_bombs;

/* Raw framebuffer value of shield/ship green color */
fb_data screen_green, screen_white;

/* For optimization, precalculate startoffset of each scanline */
unsigned int ytab[LCD_HEIGHT];

int lives = 2;
int score = 0;
int scores[3] = { 30, 20, 10 };
int level = 0;
struct highscore hiscore;
bool game_over = false;
int ship_x, old_ship_x, ship_dir, ship_acc, max_ship_speed;
int ship_frame, ship_frame_counter;
bool ship_hit;
int fire, fire_target, fire_x, fire_y;
int curr_alien, aliens_paralyzed, gamespeed;
int ufo_state, ufo_x;
bool level_finished;
bool aliens_down, aliens_right, hit_left_border, hit_right_border;


/* No standard get_pixel function yet, use this hack instead */
#if (LCD_DEPTH >= 8)

#if   defined(LCD_STRIDEFORMAT) && LCD_STRIDEFORMAT == VERTICAL_STRIDE
inline fb_data get_pixel(int x, int y)
{
    return rb->lcd_framebuffer[x*LCD_HEIGHT+y];
}
#else
inline fb_data get_pixel(int x, int y)
{
    return rb->lcd_framebuffer[ytab[y] + x];
}
#endif

#elif (LCD_DEPTH == 2)

#if (LCD_PIXELFORMAT == HORIZONTAL_PACKING)
static const unsigned char shifts[4] = {
    6, 4, 2, 0
};
/* Horizontal packing */
inline fb_data get_pixel(int x, int y)
{
    return (rb->lcd_framebuffer[ytab[y] + (x >> 2)] >> shifts[x & 3]) & 3;
}
#else
/* Vertical packing */
static const unsigned char shifts[4] = {
    0, 2, 4, 6
};
inline fb_data get_pixel(int x, int y)
{
    return (rb->lcd_framebuffer[ytab[y] + x] >> shifts[y & 3]) & 3;
}
#endif /* Horizontal/Vertical packing */

#else
    #error get_pixel: pixelformat not implemented yet
#endif


/* Draw "digits" least significant digits of num at (x,y) */
void draw_number(int x, int y, int num, int digits)
{
    int i;
    int d;

    for (i = digits - 1; i >= 0; i--) {
        d = num % 10;
        num = num / 10;
        rb->lcd_bitmap_part(invadrox_numbers, d * NUMBERS_WIDTH, 0,
                            STRIDE( SCREEN_MAIN, 
                                    BMPWIDTH_invadrox_numbers, 
                                    BMPHEIGHT_invadrox_numbers),
                            x + i * (NUMBERS_WIDTH + NUM_SPACING), y,
                            NUMBERS_WIDTH, FONT_HEIGHT);
    }
    /* Update lcd */
    rb->lcd_update_rect(x, y, 4 * NUMBERS_WIDTH + 3 * NUM_SPACING, FONT_HEIGHT);
}


inline void draw_score(void)
{
    draw_number(SCORENUM_X, SCORENUM_Y, score, 4);
    if (score > hiscore.score) {
        /* Draw new hiscore (same as score) */
        draw_number(HISCORENUM_X, SCORENUM_Y, score, 4);
    }
}


void draw_level(void)
{
    draw_number(LEVEL_X + 2 * NUM_SPACING, PLAYFIELD_Y + 2, level, 2);
}


void draw_lives(void)
{
    int i;
    /* Lives num */
    rb->lcd_bitmap_part(invadrox_numbers, lives * NUMBERS_WIDTH, 0,
                        STRIDE( SCREEN_MAIN, 
                                BMPWIDTH_invadrox_numbers, 
                                BMPHEIGHT_invadrox_numbers), 
                        PLAYFIELD_X + LIVES_X, PLAYFIELD_Y + 2,
                        NUMBERS_WIDTH, FONT_HEIGHT);

    /* Ships */
    for (i = 0; i < (lives - 1); i++) {
        rb->lcd_bitmap_part(invadrox_ships, 0, 0, 
                            STRIDE( SCREEN_MAIN, 
                                    BMPWIDTH_invadrox_ships, 
                                    BMPHEIGHT_invadrox_ships),
                            PLAYFIELD_X + LIVES_X + SHIP_WIDTH + i * (SHIP_WIDTH + NUM_SPACING),
                            PLAYFIELD_Y + 1, SHIP_WIDTH, SHIP_HEIGHT);
    }

    /* Erase ship to the right (if less than MAX_LIVES) */
    if (lives < MAX_LIVES) {
        rb->lcd_fillrect(PLAYFIELD_X + LIVES_X + SHIP_WIDTH + i * (SHIP_WIDTH + NUM_SPACING),
                         PLAYFIELD_Y + 1, SHIP_WIDTH, SHIP_HEIGHT);
    }
    /* Update lives (and level) part of screen */
    rb->lcd_update_rect(PLAYFIELD_X + LIVES_X, PLAYFIELD_Y + 1,
                        PLAYFIELD_WIDTH - 2 * LIVES_X, MAX(FONT_HEIGHT + 1, SHIP_HEIGHT + 1));
}


inline void draw_aliens(void)
{
    int i;

    for (i = 0; i < 5 * ALIENS; i++) {
        rb->lcd_bitmap_part(invadrox_aliens, aliens[i].x & 1 ? ALIEN_WIDTH : 0, 
                            aliens[i].type * ALIEN_HEIGHT,
                            STRIDE( SCREEN_MAIN, 
                                    BMPWIDTH_invadrox_aliens, 
                                    BMPHEIGHT_invadrox_aliens), 
                            PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED,
                            ALIEN_START_Y + aliens[i].y * ALIEN_HEIGHT,
                            ALIEN_WIDTH, ALIEN_HEIGHT);
    }
}


/* Return false if there is no next alive alien (round is over) */
inline bool next_alien(void)
{
    bool ret = true;

    do {
        curr_alien++;
        if (curr_alien % ALIENS == 0) {
            /* End of this row. Move up one row. */
            curr_alien -= 2 * ALIENS;
            if (curr_alien < 0) {
                /* No more aliens in this round. */
                curr_alien = 4 * ALIENS;
                ret = false;
            }
        }
    } while (aliens[curr_alien].state == DEAD && ret);

    if (!ret) {
        /* No more alive aliens. Round finished. */
        if (hit_right_border) {
            if (hit_left_border) {
                DBG("ERROR: both left and right borders are set (%d)\n", curr_alien);
            }
            /* Move down-left next round */
            aliens_right = false;
            aliens_down = true;
            hit_right_border = false;
        } else if (hit_left_border) {
            /* Move down-right next round */
            aliens_right = true;
            aliens_down = true;
            hit_left_border = false;
        } else {
            /* Not left nor right. Set down to false. */
            aliens_down = false;
        }
    }

    return ret;
}


/* All aliens have been moved.
 * Set curr_alien to first alive.
 * Return false if no-one is left alive.
 */
bool first_alien(void)
{
    int i, y;

    for (y = 4; y >= 0; y--) {
        for (i = y * ALIENS; i < (y + 1) * ALIENS; i++) {
            if (aliens[i].state != DEAD) {
                curr_alien = i;
                return true;
            }
        }
    }

    /* All aliens dead. */
    level_finished = true;

    return false;
}


bool move_aliens(void)
{
    int x, y, old_x, old_y;

    /* Move current alien (curr_alien is pointing to a living alien) */

    old_x = aliens[curr_alien].x;
    old_y = aliens[curr_alien].y;

    if (aliens_down) {
        aliens[curr_alien].y++;
        if (aliens[curr_alien].y == MAX_Y) {
            /* Alien is at bottom. Game Over. */
            DBG("Alien %d is at bottom. Game Over.\n", curr_alien);
            game_over = true;
            return false;
        }
    }

    if (aliens_right) {
        /* Moving right */
        if (aliens[curr_alien].x < MAX_X) {
            aliens[curr_alien].x++;
        }

        /* Now, after move, check if we hit the right border. */
        if (aliens[curr_alien].x == MAX_X) {
            hit_right_border = true;
        }

    } else {
        /* Moving left */
        if (aliens[curr_alien].x > 0) {
            aliens[curr_alien].x--;
        }

        /* Now, after move, check if we hit the left border. */
        if (aliens[curr_alien].x == 0) {
            hit_left_border = true;
        }
    }

    /* Erase old position */
    x = PLAYFIELD_X + LIVES_X + old_x * ALIEN_SPEED;
    y = ALIEN_START_Y + old_y * ALIEN_HEIGHT;
    if (aliens[curr_alien].y != old_y) {
        /* Moved in y-dir. Erase whole alien. */
        rb->lcd_fillrect(x, y, ALIEN_WIDTH, ALIEN_HEIGHT);
    } else {
        if (aliens_right) {
            /* Erase left edge */
            rb->lcd_fillrect(x, y, ALIEN_SPEED, ALIEN_HEIGHT);
        } else {
            /* Erase right edge */
            x += ALIEN_WIDTH - ALIEN_SPEED;
            rb->lcd_fillrect(x, y, ALIEN_SPEED, ALIEN_HEIGHT);
        }
    }

    /* Draw alien at new pos */
    x = PLAYFIELD_X + LIVES_X + aliens[curr_alien].x * ALIEN_SPEED;
    y = ALIEN_START_Y + aliens[curr_alien].y * ALIEN_HEIGHT;
    rb->lcd_bitmap_part(invadrox_aliens,
                        aliens[curr_alien].x & 1 ? ALIEN_WIDTH : 0, 
                        aliens[curr_alien].type * ALIEN_HEIGHT,
                        STRIDE( SCREEN_MAIN, 
                                BMPWIDTH_invadrox_aliens,
                                BMPHEIGHT_invadrox_aliens), 
                        x, y, ALIEN_WIDTH, ALIEN_HEIGHT);

    if (!next_alien()) {
        /* Round finished. Set curr_alien to first alive from bottom. */
        if (!first_alien()) {
            /* Should never happen. Taken care of in move_fire(). */
            return false;
        }
        /* TODO: Play next background sound */
    }

    return true;
}


inline void draw_ship(void)
{
    /* Erase old ship */
    if (old_ship_x < ship_x) {
        /* Move right. Erase leftmost part of ship. */
        rb->lcd_fillrect(old_ship_x, SHIP_Y, ship_x - old_ship_x, SHIP_HEIGHT);
    } else if (old_ship_x > ship_x) {
        /* Move left. Erase rightmost part of ship. */
        rb->lcd_fillrect(ship_x + SHIP_WIDTH, SHIP_Y, old_ship_x - ship_x, SHIP_HEIGHT);
    }

    /* Draw ship */
    rb->lcd_bitmap_part(invadrox_ships, 0, ship_frame * SHIP_HEIGHT,
                        STRIDE( SCREEN_MAIN, 
                                BMPWIDTH_invadrox_ships,
                                BMPHEIGHT_invadrox_ships), 
                        ship_x, SHIP_Y, SHIP_WIDTH, SHIP_HEIGHT);
    if (ship_hit) {
        /* Alternate between frame 1 and 2 during hit */
        ship_frame_counter++;
        if (ship_frame_counter > 2) {
            ship_frame_counter = 0;
            ship_frame++;
            if (ship_frame > 2) {
                ship_frame = 1;
            }
        }
    }

    /* Save ship_x for next time */
    old_ship_x = ship_x;
}


inline void fire_alpha(int xc, int yc, fb_data color)
{
    int oldmode = rb->lcd_get_drawmode();

    rb->lcd_set_foreground(color);
    rb->lcd_set_drawmode(DRMODE_FG);
    
    rb->lcd_mono_bitmap(invadrox_fire, xc - (FIRE_WIDTH/2), yc, FIRE_WIDTH, FIRE_HEIGHT);

    rb->lcd_set_foreground(LCD_BLACK);
    rb->lcd_set_drawmode(oldmode);
}


void move_fire(void)
{
    bool hit_green = false;
    bool hit_white = false;
    int i, j;
    static int exploding_alien = -1;
    fb_data pix;

    if (fire == S_IDLE) {
        return;
    }

    /* Alien hit. Wait until explosion is finished. */
    if (aliens_paralyzed < 0) {
        aliens_paralyzed++;
        if (aliens_paralyzed == 0) {
            /* Erase exploding_alien */
            rb->lcd_fillrect(PLAYFIELD_X + LIVES_X + aliens[exploding_alien].x * ALIEN_SPEED,
                             ALIEN_START_Y + aliens[exploding_alien].y * ALIEN_HEIGHT,
                             ALIEN_EXPLODE_WIDTH, ALIEN_HEIGHT);
            fire = S_IDLE;
            /* Special case. We killed curr_alien. */
            if (exploding_alien == curr_alien) {
                if (!next_alien()) {
                    /* Round finished. Set curr_alien to first alive from bottom. */
                    first_alien();
                }
            }
        }
        return;
    }

    if (fire == S_ACTIVE) {

        /* Erase */
        rb->lcd_vline(fire_x, fire_y, fire_y + SHOT_HEIGHT);

        /* Check top */
        if (fire_y <= SCORENUM_Y + FONT_HEIGHT + 4) {

            /* TODO: Play explode sound */

            fire = S_EXPLODE;
            fire_target = TARGET_TOP;
            fire_alpha(fire_x, fire_y, UFO_RED);
            return;
        }

        /* Move */
        fire_y -= FIRE_SPEED;

        /* Hit UFO? */
        if (ufo_state == S_ACTIVE) {
            if ((ABS(ufo_x + UFO_WIDTH / 2 - fire_x) <= UFO_WIDTH / 2) &&
                (fire_y <= UFO_Y + UFO_HEIGHT)) {
                ufo_state = S_EXPLODE;
                fire = S_EXPLODE;
                fire_target = TARGET_UFO;
                /* Center explosion */
                ufo_x -= (UFO_EXPLODE_WIDTH - UFO_WIDTH) / 2;
                rb->lcd_bitmap(invadrox_ufo_explode, ufo_x, UFO_Y - 1,
                               UFO_EXPLODE_WIDTH, UFO_EXPLODE_HEIGHT);
                return;
            }
        }

        /* Hit bomb? (check position, not pixel value) */
        for (i = 0; i < max_bombs; i++) {
            if (bombs[i].state == S_ACTIVE) {
                /* Count as hit if within BOMB_WIDTH pixels */
                if ((ABS(bombs[i].x - fire_x) < BOMB_WIDTH) &&
                    (fire_y - bombs[i].y < BOMB_HEIGHT)) {
                    /* Erase bomb */
                    rb->lcd_fillrect(bombs[i].x, bombs[i].y, BOMB_WIDTH, BOMB_HEIGHT);
                    bombs[i].state = S_IDLE;
                    /* Explode ship fire */
                    fire = S_EXPLODE;
                    fire_target = TARGET_SHIELD;
                    fire_alpha(fire_x, fire_y, LCD_WHITE);
                    return;
                }
            }
        }

        /* Check for hit*/
        for (i = FIRE_SPEED; i >= 0; i--) {
            pix = get_pixel(fire_x, fire_y + i);
            if(pix == screen_white) {
                hit_white = true;
                fire_y += i;
                break;
            }
            if(pix == screen_green) {
                hit_green = true;
                fire_y += i;
                break;
            }
        }

        if (hit_green) {
            /* Hit shield */

            /* TODO: Play explode sound */

            fire = S_EXPLODE;
            fire_target = TARGET_SHIELD;
            /* Center explosion around hit pixel */
            fire_y -= FIRE_HEIGHT / 2;
            fire_alpha(fire_x, fire_y, SLIME_GREEN);
            return;
        }

        if (hit_white) {

            /* Hit alien? */
            for (i = 0; i < 5 * ALIENS; i++) {
                if (aliens[i].state != DEAD &&
                    (ABS(fire_x - (PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED +
                                   ALIEN_WIDTH / 2)) <= ALIEN_WIDTH / 2) &&
                    (ABS(fire_y - (ALIEN_START_Y + aliens[i].y * ALIEN_HEIGHT +
                                   ALIEN_HEIGHT / 2)) <= ALIEN_HEIGHT / 2)) {

                    /* TODO: play alien hit sound */

                    if (aliens[i].state == BOMBER) {
                        /* Set (possible) alien above to bomber */
                        for (j = i - ALIENS; j >= 0; j -= ALIENS) {
                            if (aliens[j].state != DEAD) {
                                /* printf("New bomber (%d, %d)\n", j % ALIENS, j / ALIENS); */
                                aliens[j].state = BOMBER;
                                break;
                            }
                        }
                    }
                    aliens[i].state = DEAD;
                    exploding_alien = i;
                    score += scores[aliens[i].type];
                    draw_score();
                    /* Update score part of screen */
                    rb->lcd_update_rect(SCORENUM_X, SCORENUM_Y,
                                        PLAYFIELD_WIDTH - 2 * NUMBERS_WIDTH, FONT_HEIGHT);

                    /* Paralyze aliens S_EXPLODE frames */
                    aliens_paralyzed = S_EXPLODE;
                    rb->lcd_bitmap(invadrox_alien_explode,
                                        PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED,
                                        ALIEN_START_Y + aliens[i].y * ALIEN_HEIGHT,
                                        ALIEN_EXPLODE_WIDTH, ALIEN_EXPLODE_HEIGHT);
                    /* Since alien is 1 pixel taller than explosion sprite, erase bottom line */
                    rb->lcd_hline(PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED,
                                  PLAYFIELD_X + LIVES_X + aliens[i].x * ALIEN_SPEED + ALIEN_WIDTH,
                                  ALIEN_START_Y + (aliens[i].y + 1) * ALIEN_HEIGHT - 1);
                    return;
                }
            }
        }

        /* Draw shot */
        rb->lcd_set_foreground(LCD_WHITE);
        rb->lcd_vline(fire_x, fire_y, fire_y + SHOT_HEIGHT);
        rb->lcd_set_foreground(LCD_BLACK);
    } else if (fire < S_IDLE) {
        /* Count up towards S_IDLE, then erase explosion */
        fire++;
        if (fire == S_IDLE) {
            /* Erase explosion */
            if (fire_target == TARGET_TOP) {
                rb->lcd_fillrect(fire_x - (FIRE_WIDTH / 2), fire_y, FIRE_WIDTH, FIRE_HEIGHT);
            } else if (fire_target == TARGET_SHIELD) {
                /* Draw explosion with black pixels */
                fire_alpha(fire_x, fire_y, LCD_BLACK);
            }
        }
    }
}


/* Return a BOMBER alien */
inline int random_bomber(void)
{
    int i, col;

    /* TODO: Weigh higher probability near ship */
    col = rb->rand() % ALIENS;
    for (i = col + 4 * ALIENS; i >= 0; i -= ALIENS) {
        if (aliens[i].state == BOMBER) {
            return i;
        }
    }

    /* No BOMBER found in this col */

    for (i = 0; i < 5 * ALIENS; i++) {
        if (aliens[i].state == BOMBER) {
            return i;
        }
    }

    /* No BOMBER found at all (error?) */

    return -1;
}


inline void draw_bomb(int i)
{
    rb->lcd_bitmap_part(invadrox_bombs, bombs[i].type * BOMB_WIDTH,
                        bombs[i].frame * BOMB_HEIGHT,
                        STRIDE( SCREEN_MAIN, 
                                BMPWIDTH_invadrox_bombs,
                                BMPHEIGHT_invadrox_bombs), 
                        bombs[i].x, bombs[i].y,
                        BOMB_WIDTH, BOMB_HEIGHT);
    /* Advance frame */
    bombs[i].frame++;
    if (bombs[i].frame == bombs[i].frames) {
        bombs[i].frame = 0;
    }
}


void move_bombs(void)
{
    int i, j, bomber;
    bool abort;

    for (i = 0; i < max_bombs; i++) {

        switch (bombs[i].state) {

        case S_IDLE:
            if (ship_hit) {
                continue;
            }
            bomber = random_bomber();
            if (bomber < 0) {
                DBG("ERROR: No bomber available\n");
                continue;
            }
            /* x, y */
            bombs[i].x = PLAYFIELD_X + LIVES_X + aliens[bomber].x * ALIEN_SPEED + ALIEN_WIDTH / 2;
            bombs[i].y = ALIEN_START_Y + (aliens[bomber].y + 1) * ALIEN_HEIGHT;

            /* Check for duplets in x and y direction */
            abort = false;
            for (j = i - 1; j >= 0; j--) {
                if ((bombs[j].state == S_ACTIVE) &&
                    ((bombs[i].x == bombs[j].x) || (bombs[i].y == bombs[j].y))) {
                    abort = true;
                    break;
                }
            }
            if (abort) {
                /* Skip this one, continue with next bomb */
                /* printf("Bomb %d duplet of %d\n", i, j); */
                continue;
            }

            /* Passed, set type */
            bombs[i].type = rb->rand() % 3;
            bombs[i].frame = 0;
            if (bombs[i].type == 0) {
                bombs[i].frames = 3;
            } else if (bombs[i].type == 1) {
                bombs[i].frames = 4;
            } else {
                bombs[i].frames = 6;
            }

            /* Bombs away */
            bombs[i].state = S_ACTIVE;
            draw_bomb(i);
            continue;

            break;

        case S_ACTIVE:
            /* Erase old position */
            rb->lcd_fillrect(bombs[i].x, bombs[i].y, BOMB_WIDTH, BOMB_HEIGHT);

            /* Move */
            bombs[i].y += BOMB_SPEED;

            /* Check if bottom hit */
            if (bombs[i].y + BOMB_HEIGHT >= PLAYFIELD_Y) {
                bombs[i].y = PLAYFIELD_Y - FIRE_HEIGHT + 1;
                fire_alpha(bombs[i].x, bombs[i].y, LCD_WHITE);
                bombs[i].state = S_EXPLODE;
                bombs[i].target = TARGET_BOTTOM;
                break;
            }

            /* Check for green (ship or shield) */
            for (j = BOMB_HEIGHT; j >= BOMB_HEIGHT - BOMB_SPEED; j--) {
                bombs[i].target = 0;
                if(get_pixel(bombs[i].x + BOMB_WIDTH / 2, bombs[i].y + j) == screen_green) {
                    /* Move to hit pixel */
                    bombs[i].x += BOMB_WIDTH / 2;
                    bombs[i].y += j;

                    /* Check if ship is hit */
                    if (bombs[i].y > SHIELD_Y + SHIELD_HEIGHT && bombs[i].y < PLAYFIELD_Y) {

                        /* TODO: play ship hit sound */

                        ship_hit = true;
                        ship_frame = 1;
                        ship_frame_counter = 0;
                        bombs[i].state = S_EXPLODE * 4;
                        bombs[i].target = TARGET_SHIP;
                        rb->lcd_bitmap_part(invadrox_ships, 0, 1 * SHIP_HEIGHT,
                                            STRIDE( SCREEN_MAIN, 
                                                    BMPWIDTH_invadrox_ships,
                                                    BMPHEIGHT_invadrox_ships), 
                                            ship_x, SHIP_Y,
                                            SHIP_WIDTH, SHIP_HEIGHT);
                        break;
                    }
                    /* Shield hit */
                    bombs[i].state = S_EXPLODE;
                    bombs[i].target = TARGET_SHIELD;
                    /* Center explosion around hit pixel in shield */
                    bombs[i].y -= FIRE_HEIGHT / 2;
                    fire_alpha(bombs[i].x, bombs[i].y, SLIME_GREEN);
                    break;
                }
            }

            if (bombs[i].target != 0) {
                /* Hit ship or shield, continue */
                continue;
            }

            draw_bomb(i);
            break;

        default:
            /* If we get here state should be < 0, exploding */
            bombs[i].state++;
            if (bombs[i].state == S_IDLE) {
                if (ship_hit) {
                    /* Erase explosion */
                    rb->lcd_fillrect(ship_x, SHIP_Y, SHIP_WIDTH, SHIP_HEIGHT);
                    rb->lcd_update_rect(ship_x, SHIP_Y, SHIP_WIDTH, SHIP_HEIGHT);
                    ship_hit = false;
                    ship_frame = 0;
                    ship_x = PLAYFIELD_X + 2 * LIVES_X;
                    lives--;
                    if (lives == 0) {
                        game_over = true;
                        return;
                    }
                    draw_lives();
                    /* Sleep 1s to give player time to examine lives left */
                    rb->sleep(HZ);
                }
                /* Erase explosion (even if ship hit, might be another bomb) */
                fire_alpha(bombs[i].x, bombs[i].y, LCD_BLACK);
            }
            break;
        }
    }
}


inline void move_ship(void)
{
    ship_dir += ship_acc;
    if (ship_dir > max_ship_speed) {
        ship_dir = max_ship_speed;
    }
    if (ship_dir < -max_ship_speed) {
        ship_dir = -max_ship_speed;
    }
    ship_x += ship_dir;
    if (ship_x < SHIP_MIN_X) {
        ship_x = SHIP_MIN_X;
    }
    if (ship_x > SHIP_MAX_X) {
        ship_x = SHIP_MAX_X;
    }

    draw_ship();
}


/* Unidentified Flying Object */
void move_ufo(void)
{
    static int ufo_speed;
    static int counter;
    int mystery_score;

    switch (ufo_state) {

    case S_IDLE:

        if (rb->rand() % 500 == 0) {
            /* Uh-oh, it's time to launch a mystery UFO */

            /* TODO: Play UFO sound */

            if (rb->rand() % 2) {
                ufo_speed = UFO_SPEED;
                ufo_x = PLAYFIELD_X;
            } else {
                ufo_speed = -UFO_SPEED;
                ufo_x = LCD_WIDTH - PLAYFIELD_X - UFO_WIDTH;
            }
            ufo_state = S_ACTIVE;
            /* UFO will be drawn next frame */
        }
        break;

    case S_ACTIVE:
        /* Erase old pos */
        rb->lcd_fillrect(ufo_x, UFO_Y, UFO_WIDTH, UFO_HEIGHT);
        /* Move */
        ufo_x += ufo_speed;
        /* Check bounds */
        if (ufo_x < PLAYFIELD_X || ufo_x > LCD_WIDTH - PLAYFIELD_X - UFO_WIDTH) {
            ufo_state = S_IDLE;
            break;
        }
        /* Draw new pos */
        rb->lcd_bitmap(invadrox_ufo, ufo_x, UFO_Y, UFO_WIDTH, UFO_HEIGHT);
        break;

    case S_SHOWSCORE:
        counter++;
        if (counter == S_IDLE) {
            /* Erase mystery number */
            rb->lcd_fillrect(ufo_x, UFO_Y, 3 * NUMBERS_WIDTH + 2 * NUM_SPACING, FONT_HEIGHT);
            ufo_state = S_IDLE;
        }
        break;

    default:
        /* Exploding */
        ufo_state++;
        if (ufo_state == S_IDLE) {
            /* Erase explosion */
            rb->lcd_fillrect(ufo_x, UFO_Y - 1, UFO_EXPLODE_WIDTH, UFO_EXPLODE_HEIGHT);
            ufo_state = S_SHOWSCORE;
            counter = S_EXPLODE * 4;
            /* Draw mystery_score, sleep, increase score and continue */
            mystery_score = 50 + (rb->rand() % 6) * 50;
            if (mystery_score < 100) {
                draw_number(ufo_x, UFO_Y, mystery_score, 2);
            } else {
                draw_number(ufo_x, UFO_Y, mystery_score, 3);
            }
            score += mystery_score;
            draw_score();
        }
        break;
    }
}


void draw_background(void)
{

    rb->lcd_bitmap(invadrox_background, 0, 0, LCD_WIDTH, LCD_HEIGHT);
    rb->lcd_update();
}


void new_level(void)
{
    int i;

    draw_background();
    /* Give an extra life for each new level */
    if (lives < MAX_LIVES) {
        lives++;
    }
    draw_lives();

    /* Score */
    draw_score();
    draw_number(HISCORENUM_X, SCORENUM_Y, hiscore.score, 4);

    level++;
    draw_level();
    level_finished = false;

    ufo_state = S_IDLE;

    /* Init alien positions and states */
    for (i = 0; i < 4 * ALIENS; i++) {
        aliens[i].x = 0 + (i % ALIENS) * ((ALIEN_WIDTH + ALIEN_SPACING) / ALIEN_SPEED);
        aliens[i].y = 2 * (i / ALIENS);
        aliens[i].state = ALIVE;
    }
    /* Last row, bombers */
    for (i = 4 * ALIENS; i < 5 * ALIENS; i++) {
        aliens[i].x = 0 + (i % ALIENS) * ((ALIEN_WIDTH + ALIEN_SPACING) / ALIEN_SPEED);
        aliens[i].y = 2 * (i / ALIENS);
        aliens[i].state = BOMBER;
    }

    /* Init bombs to inactive (S_IDLE) */
    for (i = 0; i < MAX_BOMBS; i++) {
        bombs[i].state = S_IDLE;
    }

    /* Start aliens closer to earth from level 2 */
    for (i = 0; i < 5 * ALIENS; i++) {
        if (level < 6) {
            aliens[i].y += level - 1;
        } else {
            aliens[i].y += 5;
        }
    }

    /* Max concurrent bombs */
    max_bombs = 1;

    gamespeed = 2;

    if (level > 1) {
        max_bombs++;
    }

    /* Increase speed */
    if (level > 2) {
        gamespeed++;
    }

    if (level > 3) {
        max_bombs++;
    }

    /* Increase speed more */
    if (level > 4) {
        gamespeed++;
    }

    if (level > 5) {
        max_bombs++;
    }

    /* 4 shields */
    for (i = 1; i <= 4; i++) {
        rb->lcd_bitmap(invadrox_shield,
                            PLAYFIELD_X + i * PLAYFIELD_WIDTH / 5 - SHIELD_WIDTH / 2,
                            SHIELD_Y, SHIELD_WIDTH, SHIELD_HEIGHT);
    }

    /* Bottom line */
    rb->lcd_set_foreground(SLIME_GREEN);
    rb->lcd_hline(PLAYFIELD_X, LCD_WIDTH - PLAYFIELD_X, PLAYFIELD_Y);
    /* Restore foreground to black (for fast erase later). */
    rb->lcd_set_foreground(LCD_BLACK);

    ship_x = PLAYFIELD_X + 2 * LIVES_X;
    if (level == 1) {
        old_ship_x = ship_x;
    }
    ship_dir = 0;
    ship_acc = 0;
    ship_frame = 0;
    ship_hit = false;
    fire = S_IDLE;
    /* Start moving the bottom row left to right */
    curr_alien = 4 * ALIENS;
    aliens_paralyzed = 0;
    aliens_right = true;
    aliens_down = false;
    hit_left_border = false;
    hit_right_border = false;
    /* TODO: Change max_ship_speed to 3 at higher levels */
    max_ship_speed = 2;

    draw_aliens();

    rb->lcd_update();
}


void init_invadrox(void)
{
    int i;

    /* Seed random number generator with a "random" number */
    rb->srand(rb->get_time()->tm_sec + rb->get_time()->tm_min * 60);

    /* Precalculate start of each scanline */
    for (i = 0; i < LCD_HEIGHT; i++) {
#if (LCD_DEPTH >= 8)
        ytab[i] = i * LCD_WIDTH;
#elif (LCD_DEPTH == 2) && (LCD_PIXELFORMAT == HORIZONTAL_PACKING)
        ytab[i] = i * (LCD_WIDTH / 4);
#elif (LCD_DEPTH == 2) && (LCD_PIXELFORMAT == VERTICAL_PACKING)
        ytab[i] = (i / 4) * LCD_WIDTH;
#else
        #error pixelformat not implemented yet
#endif
    }

    rb->lcd_set_background(LCD_BLACK);
    rb->lcd_set_foreground(LCD_BLACK);

    if (highscore_load(HISCOREFILE, &hiscore, 1) < 0) {
        /* Init hiscore to 0 */
        rb->strlcpy(hiscore.name, "Invader", sizeof(hiscore.name));
        hiscore.score = 0;
        hiscore.level = 1;
    }

    /* Init alien types in aliens array */
    for (i = 0; i < 1 * ALIENS; i++) {
        aliens[i].type = 0; /* Kang */
    }
    for (; i < 3 * ALIENS; i++) {
        aliens[i].type = 1; /* Kodos */
    }
    for (; i < 5 * ALIENS; i++) {
        aliens[i].type = 2; /* Serak */
    }


    /* Save screen white color */
    rb->lcd_set_foreground(LCD_WHITE);
    rb->lcd_drawpixel(0, 0);
    rb->lcd_update_rect(0, 0, 1, 1);
    screen_white = get_pixel(0, 0);

    /* Save screen green color */
    rb->lcd_set_foreground(SLIME_GREEN);
    rb->lcd_drawpixel(0, 0);
    rb->lcd_update_rect(0, 0, 1, 1);
    screen_green = get_pixel(0, 0);

    /* Restore black foreground */
    rb->lcd_set_foreground(LCD_BLACK);

    new_level();

    /* Flash score at start */
    for (i = 0; i < 5; i++) {
        rb->lcd_fillrect(SCORENUM_X, SCORENUM_Y,
                         4 * NUMBERS_WIDTH + 3 * NUM_SPACING,
                         FONT_HEIGHT);
        rb->lcd_update_rect(SCORENUM_X, SCORENUM_Y,
                            4 * NUMBERS_WIDTH + 3 * NUM_SPACING,
                            FONT_HEIGHT);
        rb->sleep(HZ / 10);
        draw_number(SCORENUM_X, SCORENUM_Y, score, 4);
        rb->sleep(HZ / 10);
    }
}


inline bool handle_buttons(void)
{
    static unsigned int oldbuttonstate = 0;

    unsigned int released, pressed, newbuttonstate;

    if (ship_hit) {
        /* Don't allow ship movement during explosion */
        newbuttonstate = 0;
    } else {
        newbuttonstate = rb->button_status();
    }
    if(newbuttonstate == oldbuttonstate) {
        if (newbuttonstate == 0) {
            /* No button pressed. Stop ship. */
            ship_acc = 0;
            if (ship_dir > 0) {
                ship_dir--;
            }
            if (ship_dir < 0) {
                ship_dir++;
            }
        }
        /* return false; */
        goto check_usb;
    }
    released = ~newbuttonstate & oldbuttonstate;
    pressed = newbuttonstate & ~oldbuttonstate;
    oldbuttonstate = newbuttonstate;
    if (pressed) {
        if (pressed & ACTION_LEFT) {
            if (ship_acc > -1) {
                ship_acc--;
            }
        }
        if (pressed & ACTION_RIGHT) {
            if (ship_acc < 1) {
                ship_acc++;
            }
        }
        if (pressed & ACTION_FIRE) {
            if (fire == S_IDLE) {
                /* Fire shot */
                fire_x = ship_x + SHIP_WIDTH / 2;
                fire_y = SHIP_Y - SHOT_HEIGHT;
                fire = S_ACTIVE;
                /* TODO: play fire sound */
            }
        }
        if (pressed & ACTION_QUIT) {
            rb->splash(HZ * 1, "Quit");
            return true;
        }
    }
    if (released) {
        if ((released & ACTION_LEFT)) {
            if (ship_acc < 1) {
                ship_acc++;
            }
        }
        if ((released & ACTION_RIGHT)) {
            if (ship_acc > -1) {
                ship_acc--;
            }
        }
    }

check_usb:

    /* Quit if USB is connected */
    if (rb->button_get(false) == SYS_USB_CONNECTED) {
        return true;
    }

    return false;
}


void game_loop(void)
{
    int i, end;

    /* Print dimensions (just for debugging) */
    DBG("%03dx%03dx%02d\n", LCD_WIDTH, LCD_HEIGHT, LCD_DEPTH);

    /* Init */
    init_invadrox();

    while (1) {
        /* Convert CYCLETIME (in ms) to HZ */
        end = *rb->current_tick + (CYCLETIME * HZ) / 1000;

        if (handle_buttons()) {
            return;
        }

        /* Animate */
        move_ship();
        move_fire();

        /* Check if level is finished (marked by move_fire) */
        if (level_finished) {
            /* TODO: Play level finished sound */
            new_level();
        }

        move_ufo();

        /* Move aliens */
        if (!aliens_paralyzed && !ship_hit) {
            for (i = 0; i < gamespeed; i++) {
                if (!move_aliens()) {
                    if (game_over) {
                        return;
                    }
                }
            }
        }

        /* Move alien bombs */
        move_bombs();
        if (game_over) {
            return;
        }

        /* Update "playfield" rect */
        rb->lcd_update_rect(PLAYFIELD_X, SCORENUM_Y + FONT_HEIGHT,
                            PLAYFIELD_WIDTH,
                            PLAYFIELD_Y + 1 - SCORENUM_Y - FONT_HEIGHT);

        /* Wait until next frame */
        DBG("%ld (%d)\n", end - *rb->current_tick, (CYCLETIME * HZ) / 1000);
        if (TIME_BEFORE(*rb->current_tick, end)) {
            rb->sleep(end - *rb->current_tick);
        } else {
            rb->yield();
        }

    } /* end while */
}


/* this is the plugin entry point */
enum plugin_status plugin_start(UNUSED const void* parameter)
{
    rb->lcd_setfont(FONT_SYSFIXED);
    /* Turn off backlight timeout */
    backlight_force_on(); /* backlight control in lib/helper.c */

    /* now go ahead and have fun! */
    game_loop();

    /* Game Over. */
    /* TODO: Play game over sound */
    rb->splash(HZ * 2, "Game Over");
    if (score > hiscore.score) {
        /* Save new hiscore */
        highscore_update(score, level, "Invader", &hiscore, 1);
        highscore_save(HISCOREFILE, &hiscore, 1);
    }

    /* Restore user's original backlight setting */
    rb->lcd_setfont(FONT_UI);
    /* Turn on backlight timeout (revert to settings) */
    backlight_use_settings(); /* backlight control in lib/helper.c */

    return PLUGIN_OK;
}



/**
 * GNU Emacs settings: Kernighan & Richie coding style with
 *                     4 spaces indent and no tabs.
 * Local Variables:
 *  c-file-style: "k&r"
 *  c-basic-offset: 4
 *  indent-tabs-mode: nil
 * End:
 */
