#include <stdint.h>
#include <stdbool.h>
#include <msp430.h>
#include "imagedata.h"

#define LEFT_BIT 0x02
#define RIGHT_BIT 0x20
#define DOWN_BIT 0x01
#define ROTATE_BIT 0x10
#define INPUT_BITS (LEFT_BIT | RIGHT_BIT | DOWN_BIT | ROTATE_BIT)

#define interrupt(x) void __attribute__((interrupt (x)))

#define delay_us(n) __delay_cycles((n) * 16)

#define LAST_LINE 255

int16_t y = LAST_LINE;
uint8_t frame = 0;
uint8_t random = 3;

volatile bool vblank = false;

uint16_t well_line = 0x3C3;
uint16_t well_vert_line;
uint16_t bmp_line[4] = { 0 };

#define WELL_HEIGHT 22
#define PIECE_BLOCKS 4
// +1

void delay_ms(uint16_t ms) {
    while (ms--)
        __delay_cycles(16000);
}

void mymemcpy(void *dest, void *src, uint16_t num) {
    char *dp = dest, *sp = src;
    while (num--)
        *dp++ = *sp++;
}

struct block {
    int8_t x, y, shim_dir;
};

struct piece {
    int8_t x, y;
    bool symmetrical;
    struct block blocks[PIECE_BLOCKS];
};

const struct piece piece_data[] = {
    {
        .x = 11, .y = 4,
        .blocks = { {-1, 0, 1}, {0, 0, 1},
                    {1, 0, 3},  {2, 0, 3} }
    },
    {
        .x = 11, .y = 4,
        .blocks = { {-1, -1, 2}, {-1, 0, 1},
                    {0, 0, 1}, {1, 0, 3} }
    },
    {
        .x = 11, .y = 4,
        .blocks = { {1, -1, 2}, {-1, 0, 1},
                    {0, 0, 1}, {1, 0, 3} }
    },
    {
        .symmetrical = true,
        .x = 11, .y = 4,
        .blocks = { {0, -1, 1}, {1, -1, 2},
                    {0, 0, 0}, {1, 0, 3} }
    },
    {
        .x = 11, .y = 4,
        .blocks = { {0, -1, 2}, {1, -1, 3},
                    {-1, 0, 1}, {0, 0, 0} }
    },
    {
        .x = 11, .y = 4,
        .blocks = { {0, -1, 2}, {-1, 0, 1},
                    {0, 0, 0}, {1, 0, 3} }
    },
    {
        .x = 11, .y = 4,
        .blocks = { {-1, -1, 1}, {0, -1, 2},
                    {0, 0, 0}, {1, 0, 3} }
    },
};

struct piece *next_piece = (struct piece *)&piece_data[4];
struct piece cur_piece, temp_piece;

volatile uint16_t debug[8];

volatile uint16_t well[WELL_HEIGHT] = { 0 };
volatile uint16_t well_horz[WELL_HEIGHT] = { 0 }; // --
volatile uint16_t well_vert[WELL_HEIGHT] = { 0 }; // |

const uint16_t bits[] = {
    1 << 0, 1 << 1, 1 << 2, 1 << 3, 1 << 4, 1 << 5, 1 << 6, 1 << 7,
    1 << 8, 1 << 9, 1 << 10, 1 << 11, 1 << 12, 1 << 13, 1 << 14, 1 << 15,
};

#define OP_SET 0
#define OP_CLEAR 1
#define OP_CHECK 2

bool bitmap_op(volatile uint16_t *bitmap, int8_t x, int8_t y, int op) {
    if (x < 0 || x >= 15 || y < 0 || y >= WELL_HEIGHT)
        return true;

    uint16_t mask = bits[x];

    if (op == OP_CHECK)
        return x >= 10 || (bitmap[y] & mask) != 0;
    else if (op == OP_SET)
        bitmap[y] |= mask;
    else if (op == OP_CLEAR)
        bitmap[y] &= ~mask;

    return false;
}

void draw_block(struct block *b, int8_t ox, int8_t oy, int op) {
    int8_t x = ox + b->x, y = oy + b->y;
    bitmap_op(well, x, y, op);
    
    switch (b->shim_dir) {
        case 0:
            bitmap_op(well_horz, x, y, op);
            break;
        case 1:
            bitmap_op(well_vert, x, y, op);
            break;
        case 2:
            bitmap_op(well_horz, x, y + 1, op);
            break;
        case 3:
            bitmap_op(well_vert, x - 1, y, op);
            break;
    }
}

void draw_piece(struct piece *p, int op) {
    int i;
    for (i = 0; i < PIECE_BLOCKS; i++)
        draw_block(&p->blocks[i], p->x, p->y, op);

    //draw_block(
    //bitmap_op(well, p->x, p->y, op);
    //for (i = 0; i < PIECE_BLOCKS; i++)
    //    bitmap_op(well, p->x + p->blocks[i].x, p->y + p->blocks[i].y, op);
}

void drop_next_piece() {
    mymemcpy(&cur_piece, next_piece, sizeof(struct piece));
    cur_piece.x = 5;
    cur_piece.y = 2;
    next_piece = (struct piece *)&piece_data[random % 7];
}

void rotate_piece() {
    int i;
    struct block *pts = temp_piece.blocks;

    for (i = 0; i < PIECE_BLOCKS; i++) {
        int8_t tmp = pts[i].y;
        pts[i].y = pts[i].x;
        pts[i].x = -tmp;
        pts[i].shim_dir = (pts[i].shim_dir + 1) & 3;
    }
}

bool collision() {
    if (bitmap_op(well, temp_piece.x, temp_piece.y, OP_CHECK))
        return true;
    
    int i;
    for (i = 0; i < PIECE_BLOCKS; i++) {
        if (bitmap_op(well,
            temp_piece.x + temp_piece.blocks[i].x,
            temp_piece.y + temp_piece.blocks[i].y,
            OP_CHECK))
        {
            return true;
        }
    }

    return false;
}

void reset_temp_piece() {
    mymemcpy(&temp_piece, &cur_piece, sizeof(struct piece));
}

void apply_temp_piece() {
    mymemcpy(&cur_piece, &temp_piece, sizeof(struct piece));
}

void clear_line(y) {
    int i;
    for (i = y; i >= 0; i--) {
        well[i] = well[i - 1];
        well_horz[i] = well_horz[i - 1];
        well_vert[i] = well_vert[i - 1];
    }

    if (y < WELL_HEIGHT - 1)
        well_horz[y + 1] = 0;

    well[0] = 0;
    well_horz[0] = 0;
    well_vert[0] = 0;
}

uint8_t prev_input_down;
uint8_t down_frame = 10;

int main() {
    // Disable watchdog
    WDTCTL = WDTPW | WDTHOLD;
    
    // 16MHz clock
    BCSCTL1 = CALBC1_16MHZ;
    DCOCTL = CALDCO_16MHZ;
    
    // Small output on P1.2 (TA0.1), big output on P2.0
    P1SEL = 0x04; // Timer output on P1.2
    P1SEL2 = 0;
    P1OUT = 0;
    P1DIR = 0x04;

    P2SEL = 0;
    P2SEL2 = 0;
    P2OUT = 0;
    P2DIR = 0x01;

    // Pull-up resistors for controller
    P1OUT |= INPUT_BITS;
    P1REN |= INPUT_BITS;

    TACTL |= TACLR; // Clear timer
    TACCTL0 = CCIE; // Enable timer period interrupt
    TACCTL1 = OUTMOD_3; // Reset on TACCR0 - set on TACCR1
    TACCR2 = 12 * 16; // Line generation interrupt @12 + alpha us
    TACCR0 = 64 * 16; // Force interrupt soon to setup
    
    __enable_interrupt();

    TACTL |= MC_1 | TASSEL_2; // Enable timer, count up to TACCR0, SMCLK source

    drop_next_piece();

    bool check_clear_line = false;

    while (true) {
        while (!vblank)
            __bis_status_register(LPM0_bits | GIE);

        vblank = false;

        uint8_t input_down = (~P1IN) & INPUT_BITS;
        uint8_t input_press = input_down & ~prev_input_down;

        if (input_down != prev_input_down)
            random ^= (random >> 4) ^ frame;
        random++;

        prev_input_down = input_down;

        draw_piece(&cur_piece, OP_CLEAR);
        draw_piece(next_piece, OP_CLEAR);

        reset_temp_piece();

        if (input_press & LEFT_BIT)
            temp_piece.x--;
        else if (input_press & RIGHT_BIT)
            temp_piece.x++;
        
        if (collision())
            reset_temp_piece();
        else
            apply_temp_piece();

        if (input_press & ROTATE_BIT && !cur_piece.symmetrical)
            rotate_piece();

        if (collision())
            reset_temp_piece();
        else
            apply_temp_piece();

        bool drop_next_now = false;

        if ((input_press & DOWN_BIT) || frame == down_frame) {
            if (input_down & DOWN_BIT)
                down_frame += 4;
            else
                down_frame += 20;

            temp_piece.y += 1;
            drop_next_now = collision();

            if (drop_next_now) {
                check_clear_line = true;
                reset_temp_piece();

                draw_piece(&cur_piece, OP_SET);
            }
        }

        apply_temp_piece();
        
        if (check_clear_line) {  
            check_clear_line = false;

            int ty;
            for (ty = WELL_HEIGHT - 1; ty > 1; ty--) {
                if (well[ty] == 0x3FF) {
                    clear_line(ty);
                    check_clear_line = true;
                    break;
                }
            }
        }

        if (drop_next_now)
            drop_next_piece();
        
        draw_piece(&cur_piece, OP_SET);
        draw_piece(next_piece, OP_SET);
    }

    return 0;
}

//#define FULL_SCAN 1017
#define FULL_SCAN (64 * 16)
//#define SYNC_LEN 75
#define SYNC_LEN (3 * 16)

int16_t well_y, well_cnt, bmp_offs, bmp_cnt;

// Sync pulse interrupt
interrupt (TIMER0_A0_VECTOR) timera0_isr() {
    switch (y) {
        case 10:
            TACCTL2 |= CCIE; // Enable line generation interrupt
            break;

        case 247:
            TACCTL2 &= ~CCIE;
            TACCR1 = (FULL_SCAN - SYNC_LEN); // Toggle SYNC_LEN before end
            break;

        case 248:
            TACCR1 = SYNC_LEN; // Toggle after SYNC_LEN
            
            frame++;
            vblank = true;

            well_line = 0;
            well_vert_line = 0;
            bmp_line[0] = bmp_line[1] = bmp_line[2] = bmp_line[3] = 0;

            __bic_status_register_on_exit(LPM0_bits);

            break;

        case 258:
            y = 0;

            well_y = 2;
            well_cnt = 0;
            bmp_offs = -bmp_playfield_wpl * 6;
            bmp_cnt = 0;

            break;
    }

    y++;
    
    if (y < 15 || y >= 248)
        return;

    // LINE UPDATE - must use at most 12*16 - x cycles (~167 cycles)
    
    if (well_y < 22) {
        if (well_cnt == 10) {
            well_cnt = 0;
            well_y++;
            well_line = well_horz[well_y];
            well_vert_line = 0;
        } else if (well_cnt > 1) {
            well_line = well[well_y];
            well_vert_line = well_vert[well_y];
        }
    } else {
        if (well_cnt & 1) {
            well_line = 0x3FF;
            well_vert_line = 0x1FF;
        } else {
            well_line = 0;
            well_vert_line = 0;
        }
    }

    well_cnt++;

    if (bmp_cnt == 2) {
        bmp_offs += bmp_playfield_wpl;
        bmp_cnt = 0;
    }
    
    if (bmp_offs >= 0 && bmp_offs < sizeof(bmp_playfield) / sizeof(uint16_t)) {
        bmp_line[0] = bmp_playfield[bmp_offs];
        bmp_line[1] = bmp_playfield[bmp_offs + 1];
        bmp_line[2] = bmp_playfield[bmp_offs + 2];
        bmp_line[3] = bmp_playfield[bmp_offs + 3];
    } else {
        bmp_line[0] = bmp_line[1] = bmp_line[2] = bmp_line[3] = 0;
    }

    bmp_cnt++;
}

