#include <stdio.h>
#include <stdint.h>
#include <cache.h>
#include <vga.h>
#include <switches.h>
#include <swap.h>
#include <defs.h>
#include <perf.h>

#define PICMR 0x4800
#define PICSR 0x4802
#define BUTTONS_IRQ 1<<3
#define IEE 1<<2

#ifdef __GECKO5__
#define LEFT 0x20
#define RIGHT 0x40
#define UP 0x200
#define DOWN 0x100
#define RESET 0x80
#define ZOOMIN 0x08
#define ZOOMOUT 0x02
#else
#define LEFT 0x2
#define RIGHT 0x1
#define UP 0x20
#define DOWN 0x10
#define RESET 0x0
#define ZOOMIN 0x04
#define ZOOMOUT 0x08
#endif

// Constants describing the output device
const int SCREEN_WIDTH = 512;   //!< screen width
const int SCREEN_HEIGHT = 512;  //!< screen height

// Constants describing the initial view port on the fractal function
const uint32_t FRAC_WIDTH = 0x30000000; 
const uint32_t CX_0 = 0xe0000000;       
const uint32_t CY_0 = 0xe8000000;       
const uint16_t N_MAX = 64;              

// global variables indicating the zoom factor and x- and y- offset for the fractal
__global uint32_t delta, cxOff, cyOff, redraw;

void buttonsHandler() {
  volatile uint32_t * switches = (uint32_t *) SWITCHES_BASE_ADDRESS;
  uint32_t buttonsPressed = swap_u32(switches[BUTTONS_PRESSED_IRQ_ID]);
  printf("latency = %d cycles\n" , swap_u32(switches[IRQ_LATENCY_ID]));
  if ((buttonsPressed&RESET) != 0) {
    delta = FRAC_WIDTH / SCREEN_WIDTH;
    cxOff = 0;
    cyOff = 0;
    redraw = 1;
    return;
  }
  if ((buttonsPressed&LEFT) != 0 && (cxOff > 0)) cxOff -= 5*delta;
  if ((buttonsPressed&RIGHT) != 0) cxOff += 5*delta;
  if ((buttonsPressed&UP) != 0 && (cyOff > 0)) cyOff -= 5*delta;
  if ((buttonsPressed&DOWN) != 0) cyOff += 5*delta;
  if ((buttonsPressed&ZOOMOUT) != 0 && (delta < FRAC_WIDTH / SCREEN_WIDTH)) {
    delta *= 2;
    cxOff = (cxOff > (SCREEN_WIDTH/4)*delta) ? cxOff - (SCREEN_WIDTH/4)*delta : 0;
    cyOff = (cyOff > (SCREEN_WIDTH/4)*delta) ? cyOff - (SCREEN_WIDTH/4)*delta : 0;
  }
  if ((buttonsPressed&ZOOMIN) != 0 && (delta/2 > 0)) {
    cxOff += (SCREEN_WIDTH/4)*delta;
    cyOff += (SCREEN_WIDTH/4)*delta;
    delta /= 2;
  }
  redraw = 1;
}

void external_interrupt_handler() {
  uint32_t irqSource = SPR_READ(PICSR);
  if ((irqSource && BUTTONS_IRQ) != 0) buttonsHandler();
}

void drawFractal(uint32_t *frameBuffer) {
  printf("Starting drawing a fractal\n");
  uint32_t color = (2<<16) | N_MAX;
  uint32_t * pixels = frameBuffer;
  asm volatile ("l.nios_crc r0,%[in1],%[in2],0x21"::[in1]"r"(color),[in2]"r"(delta));
  uint32_t cy = CY_0 + cyOff;
  for (int k = 0 ; k < SCREEN_HEIGHT ; k++) {
    uint32_t cx = CX_0 + cxOff;
    for (int i = 0 ; i < SCREEN_WIDTH ; i+=2) {
      asm volatile ("l.nios_rrr %[out1],%[in1],%[in2],0x20":[out1]"=r"(color):[in1]"r"(cx),[in2]"r"(cy));
      *(pixels++) = color;
      cx += delta << 1;
    }
    cy += delta;
  }
  dcache_flush();
  printf("Done\n");
}

int main() {
  icache_write_cfg( CACHE_DIRECT_MAPPED | CACHE_SIZE_8K );
  dcache_write_cfg( CACHE_DIRECT_MAPPED | CACHE_SIZE_8K | CACHE_WRITE_BACK );
  icache_enable(1);
  dcache_enable(1);

  volatile unsigned int *vga = (unsigned int *) 0X50000020;
  uint32_t frameBuffer[(SCREEN_WIDTH * SCREEN_HEIGHT)/2];

  vga_clear();

  /* Enable the vga-controller's graphic mode */
  vga[0] = swap_u32(SCREEN_WIDTH);
  vga[1] = swap_u32(SCREEN_HEIGHT);
  vga[3] = swap_u32((unsigned int)&frameBuffer[0]); // disable the vga controller by commenting this line

  delta = FRAC_WIDTH / SCREEN_WIDTH;
  cxOff = 0;
  cyOff = 0;
  redraw = 0;
  /* draw the fractal */
  drawFractal(frameBuffer);

  volatile uint32_t * switches = (uint32_t *) SWITCHES_BASE_ADDRESS;
  uint32_t super;
  uint32_t oldbuttons;
  uint32_t buttons = oldbuttons = switches[BUTTONS_STATE_ID];
  
  /* enable the pic */
  SPR_WRITE(PICMR, BUTTONS_IRQ);
  super = SPR_READ(17);
  super |= IEE;
  SPR_WRITE(17,super);
  switches[BUTTONS_PRESSED_IRQ_ID] = swap_u32(LEFT|RIGHT|UP|DOWN|RESET|ZOOMIN|ZOOMOUT);
  
  do {
    buttons = switches[BUTTONS_STATE_ID];
    if (buttons != oldbuttons) {
      oldbuttons = buttons;
      printf("0x%08X\n", swap_u32(buttons));
    }
    if (redraw == 1) {
      redraw = 0;
      perf_print_cycles(PERF_COUNTER_RUNTIME,"irq runtime");
      drawFractal(frameBuffer);
    }
  } while(1);
}
