//----- Include Files ---------------------------------------------------------
#include "global.h"		// include our global settings

#include <avr/io.h>		// include I/O definitions (port names, pin names, etc)
#include <avr/interrupt.h>	// include interrupt support
//#include <util/delay.h>

//#include "uart.h"		// include uart function library
//#include "rprintf.h"	// include printf function library
#include "timer.h"		// include A/D converter function library
//#include "i2c.h"
//#include "math.h"
//#include "stdlib.h"

//#define I2C_DEVICE_ADDR 0x16

/* hw configuration */

#define DATA_DDR DDRC
#define DATA_PORT PORTC

#define CTL_DDR DDRA
#define CTL_PORT PORTA

// pins to control the flipflops (data -> ULNs)
#define PIN_74HC574N_OUTPUTENABLE PA0

/* the 4017 decade counter toggles the clock
 * of each flipflop subsequently. Thereby the 4017
 * controls which flipflops takes over the data on
 * the bus.
 */
#define PIN_4017_CLOCK PA1
#define PIN_4017_CLEAR PA2

/*
 * pins to control the shirft register,
 * which selects the active mosfet
 */
#define PIN_74HC164_INPUT PA4
#define PIN_74HC164_CLOCK PA5


#define nop() \
   asm volatile ("nop")

#define NUM_BYTES 10 // must be 10 with 4017 decade counter
#define SIZE 5
#define WIDTH_Y (SIZE)
#define WIDTH_X (SIZE)
#define HEIGHT_Z (SIZE)

#define COLORS 3
#define PWM_CYCLE 32
#define NUM_GRADIENTS 5

// size of rgb color sequence
#define NUM_COLOR_SEQUENCE 6
// the array holding the color (gradient) value (0..MUM_GRADIENTS) for all leds
// 0 means OFF
// GRADIENTS 1... NUM_GRADIENTS means ON in gradients of brightness
u08 memory[WIDTH_X][WIDTH_Y][HEIGHT_Z][COLORS];

// the array holding all 0/1 values for a whole cube
// values are packed in bytes
// example: 00001111
// first 4 leds off
// last 4 leds on
// double buffer * num. of gradient values * num. layers * num bytes
volatile u08 double_buffer[2][NUM_GRADIENTS][HEIGHT_Z][NUM_BYTES]; // 2 * 5*5 * 10  (lt half the available RAM)


u08 gradientTable[NUM_GRADIENTS] = {1, 2, 6, 13, 31};
// selects the active buffer
volatile u08 active_buffer = 0;

volatile u08 layer = 0;
volatile u08 pwm_counter = 0;

volatile uint32_t milliseconds = 0;


// variables for animation program
s08 rgbColorSequence[NUM_COLOR_SEQUENCE][COLORS] = {
    {NUM_GRADIENTS, 0, 0},
    {0, NUM_GRADIENTS, 0},
    {0, 0, NUM_GRADIENTS},
    {NUM_GRADIENTS, NUM_GRADIENTS, 0},
    {0, NUM_GRADIENTS, NUM_GRADIENTS},
    {NUM_GRADIENTS, 0, NUM_GRADIENTS}
};



//////////////////////////
// functions
//////////////////////////

inline void toggleCtlPins(u08 pins) {
    CTL_PORT |= pins;
    delay_us(20);
    CTL_PORT &= ~(pins);
}

/**
 * reset 4017
 * to 9 !!! so next count will result in 0
 */
inline void init4017(void) {
    u08 i;
    toggleCtlPins(_BV(PIN_4017_CLEAR));
    // count to 9 so next clock cycle will count "up" to 0
    for (i = 0; i < 9; i++) {
        toggleCtlPins(_BV(PIN_4017_CLOCK));
    }
}

inline void toggle74HC164CLOCK(void) {
    toggleCtlPins(_BV(PIN_74HC164_CLOCK));
}

inline void clear74HC164(void) {
    u08 i;


    CTL_PORT |= _BV(PIN_74HC164_INPUT);
    // push in 11111, since mosfets will switch on logical high
    for (i = 0; i < SIZE; i++) {
        toggle74HC164CLOCK();
        delay_us(20);
    }

}

inline u08 pwmToGradient(u08 pwm) {
    u08 g;
    for (g = 0; g < NUM_GRADIENTS - 1; g++) {
        if (pwm <= gradientTable[g]) {
            return g;
        }
    }
    return NUM_GRADIENTS - 1;
}

/**
 * configures and displays one layer
 */
void callback_timer0ovf(void) {

    u08 i;
    u08 gradient = pwmToGradient(pwm_counter);

    if (layer == 0) {
        // for active layer a 0 must be pushed into the shift register
        CTL_PORT &= ~(_BV(PIN_74HC164_INPUT));
    } else {
        // input of shift register is 1 for all other loop iterations
        CTL_PORT |= _BV(PIN_74HC164_INPUT);
    }


    // turn off all leds by disabling output of the flip flops
    CTL_PORT |= _BV(PIN_74HC574N_OUTPUTENABLE);

    // select the next cube layer by shifting the register
    toggle74HC164CLOCK();

    for (i = 0; i < NUM_BYTES; i++) {
        // set current byte as output on port
        DATA_PORT = double_buffer[active_buffer][gradient][layer][i];
        // toggle clock of 4017, so next flipflop stores
        // the current byte on the bus
        toggleCtlPins(_BV(PIN_4017_CLOCK));
    }

    // enable all flip flops enable output (by pulling to gnd)
    CTL_PORT &= ~(_BV(PIN_74HC574N_OUTPUTENABLE));

    layer++;
    if (layer >= SIZE) {
        layer = 0;
    }

    pwm_counter++;
    if (pwm_counter >= PWM_CYCLE) {
        pwm_counter = 0;
    }

}

void callback_timer2ovf(void) {
    milliseconds++;
}

/*
 * Do all the startup-time peripheral initializations.
 */
inline void initIO(void) {

    /* set all control ports as output. */

    /* 74HC164 register for cube level selection */
    CTL_DDR |= _BV(PIN_74HC164_CLOCK);
    CTL_DDR |= _BV(PIN_74HC164_INPUT);
    //CTL_DDR |= _BV(PIN_74HC164_CLEAR);

    // pins to control the flipflops (data -> ULNs)
    CTL_DDR |= _BV(PIN_74HC574N_OUTPUTENABLE);
    CTL_DDR |= _BV(PIN_4017_CLOCK);
    CTL_DDR |= _BV(PIN_4017_CLEAR);

    /* init 4017 decate counter */
    init4017();

    /** clear the shift register */
    clear74HC164();

    /* set all data ports as output */
    DATA_DDR = 0xFF;

    // led bus off
    DATA_PORT = 0;
}




// ************ I2C Slave operations ************

// this function will run when a master somewhere else on the bus
// addresses us and wishes to write data to us

/*
void i2c_incomming_call(u08 receiveDataLength, u08* recieveData) {
    if (receiveDataLength == 1) {
        if (recieveData[0] == 0) {
            brake();
        } else if (recieveData[0] == 1) {
            neutral();
        } else if (recieveData[0] == 2) {
            stepCount[LEFT] = 0;
            stepCount[RIGHT] = 0;
        }
    }

    if (receiveDataLength == 2) {
        if (recieveData[0] == 2) {
            setStepperHalfFull(LEFT, recieveData[1]);
            setStepperHalfFull(RIGHT, recieveData[1]);
        }
    }


    if (receiveDataLength == 5) { // leave index 0 unused for other commands
        v = (s16) (((u16) recieveData[1] << 8) | recieveData[2]);
        o = (s16) (((u16) recieveData[3] << 8) | recieveData[4]);
    }
}
 */
// this function will run when a master somewhere else on the bus
// addresses us and wishes to read data from us

/*
u08 i2c_incomming_request(u08 transmitDataLengthMax, u08* transmitData) {
    transmitData[0] = stepCount[LEFT] >> 8;
    transmitData[1] = stepCount[LEFT];
    transmitData[2] = stepCount[RIGHT] >> 8;
    transmitData[3] = stepCount[RIGHT];
    return 4;
}
 */

/**
 * sets the buffer with index bufferIndex.
 *
 * the image is read from memory and transformed
 * to a representation in the double buffer
 *
 * the double buffer consists of two identical areas.
 * while one buffer is displayed the next images
 * is prepared in the other. Once that image is constructed
 * the buffers are switches atomically.
 *
 * the memory holds a value of 0 to 5 (6 possible values) for each led(color)
 * 0 meaning OFF, and 5 meaning full ON. The intermediate steps
 * are gradient values for shades of brightness.
 *
 * this operation sets the buffer with currentIndex which itself is multi-
 * dimensional array gradients * layers * bytes
 * in it a precalulated byte representation of the data ports gets set
 */
void memoryToBuffer(u08 bufferIndex) {
    u08 gradient;
    u08 byte_index;
    u08 bit_index;
    u08 x, y, z, c;
    u16 temp;
    for (z = 0; z < HEIGHT_Z; z++) {
        for (x = 0; x < WIDTH_X; x++) {
            for (y = 0; y < WIDTH_Y; y++) {
                for (c = 0; c < COLORS; c++) {
                    temp = (y * (WIDTH_X * COLORS) + x * (COLORS) + c);
                    byte_index = temp / 8;
                    bit_index = temp % 8;
                    for (gradient = 0; gradient < NUM_GRADIENTS; gradient++) {
                        if (memory[x][y][z][c] != 0 && memory[x][y][z][c] >= gradient) {
                            double_buffer[bufferIndex][gradient][z][byte_index] |= _BV(bit_index);
                        } else {
                            double_buffer[bufferIndex][gradient][z][byte_index] &= ~(_BV(bit_index));
                        }
                    }
                }
            }
        }
    }
}

void program_solid(s08 red, s08 green, s08 blue) {
    u08 x, y, z;

    // init memory
    for (x = 0; x < WIDTH_X; x++) {
        for (y = 0; y < WIDTH_Y; y++) {
            for (z = 0; z < HEIGHT_Z; z++) {
                memory[x][y][z][0] = blue;
                memory[x][y][z][1] = green;
                memory[x][y][z][2] = red;
            }
        }
    }
}

void program_rainbow(u08 w) {
    u08 x, y, z, c;
    for (x = 0; x < WIDTH_X; x++) {
        for (y = 0; y < WIDTH_Y; y++) {
            for (z = 0; z < HEIGHT_Z; z++) {
                for (c = 0; c < COLORS; c++) {
                    if ((z + w) % 3 == c) {
                        memory[x][y][z][c] = NUM_GRADIENTS; // max
                    } else {
                        memory[x][y][z][c] = 0; // OFF
                    }
                }
            }
        }

    }
}

/**
 * Displays a ball in the center of the cube.
 * This function is specifically designed for a 5x5x5 cube.
 * @param red 0 to NUM_GRADIENTS
 * @param green 0 to NUM_GRADIENTS
 * @param blue 0 to NUM_GRADIENTS
 * @param blackothers whether to black out leds outside of the ball
 */
void program_ball(s08 red, s08 green, s08 blue, u08 blackothers) {
    s08 x, y, z, c;
    if (blackothers) {
        program_solid(0, 0, 0);
    }

    for (c = 2; c >= 0; c--) {
        for (x = 0; x < WIDTH_X; x++) {
            for (y = 0; y < WIDTH_Y; y++) {
                for (z = 0; z < HEIGHT_Z; z++) {

                    if (((x - 2)*(x - 2) + (y - 2)*(y - 2) + (z - 2)*(z - 2)) <= c * c) {
                        memory[x][y][z][2] = red;
                        memory[x][y][z][1] = green;
                        memory[x][y][z][0] = blue;
                    }
                }
            }

        }
    }
}

/**
 * Displays a gradient cube with all possible colors.
 * 
 * colors range from 0 to WIDTH_X-1, WIDHT_Y-1, HEIGHT_Z-1
 * (which in all cases equals NUM_GRADIENTS-1 = 4)
 */
void program_gradient(void) {
    u08 x, y, z;
    for (x = 0; x < WIDTH_X; x++) {
        for (y = 0; y < WIDTH_Y; y++) {
            for (z = 0; z < HEIGHT_Z; z++) {
                memory[x][y][z][0] = x;
                memory[x][y][z][1] = y;
                memory[x][y][z][2] = z;
            }
        }
    }
}

void program_rotation_left45(void) {
    u08 s, z;

    u08 backup[HEIGHT_Z][COLORS];
    u08 outer_sequence[8][2] = {
        {0, 0},
        {0, 2},
        {0, 4},
        {2, 4},
        {4, 4},
        {4, 2},
        {4, 0},
        {2, 0}
    };
    u08 inner_sequence[8][2] = {
        {1, 1},
        {1, 2},
        {1, 3},
        {2, 3},
        {3, 3},
        {3, 2},
        {3, 1},
        {2, 1}
    };
    u08 black_leds[8][2] = {
        {0, 1},
        {0, 3},
        {1, 4},
        {3, 4},
        {4, 3},
        {4, 1},
        {3, 0},
        {1, 0}
    };

    // black out leds
    for (s = 0; s < 8; s++) {
        for (z = 0; z < HEIGHT_Z; z++) {
            memory[black_leds[s][0]][black_leds[s][1]][z][0] = 0;
            memory[black_leds[s][0]][black_leds[s][1]][z][1] = 0;
            memory[black_leds[s][0]][black_leds[s][1]][z][2] = 0;
        }
    }

    // outer ring
    for (z = 0; z < HEIGHT_Z; z++) {
        backup[z][0] = memory[outer_sequence[0][0]][outer_sequence[0][1]][z][0];
        backup[z][1] = memory[outer_sequence[0][0]][outer_sequence[0][1]][z][1];
        backup[z][2] = memory[outer_sequence[0][0]][outer_sequence[0][1]][z][2];
    }
    for (s = 0; s < 8 - 1; s++) {
        for (z = 0; z < HEIGHT_Z; z++) {
            memory[outer_sequence[s][0]][outer_sequence[s][1]][z][0] = memory[outer_sequence[s + 1][0]][outer_sequence[s + 1][1]][z][0];
            memory[outer_sequence[s][0]][outer_sequence[s][1]][z][1] = memory[outer_sequence[s + 1][0]][outer_sequence[s + 1][1]][z][1];
            memory[outer_sequence[s][0]][outer_sequence[s][1]][z][2] = memory[outer_sequence[s + 1][0]][outer_sequence[s + 1][1]][z][2];
        }
    }
    for (z = 0; z < HEIGHT_Z; z++) {
        memory[outer_sequence[7][0]][outer_sequence[7][1]][z][0] = backup[z][0];
        memory[outer_sequence[7][0]][outer_sequence[7][1]][z][1] = backup[z][1];
        memory[outer_sequence[7][0]][outer_sequence[7][1]][z][2] = backup[z][2];
    }

    // inner ring
    for (z = 0; z < HEIGHT_Z; z++) {
        backup[z][0] = memory[inner_sequence[0][0]][inner_sequence[0][1]][z][0];
        backup[z][1] = memory[inner_sequence[0][0]][inner_sequence[0][1]][z][1];
        backup[z][2] = memory[inner_sequence[0][0]][inner_sequence[0][1]][z][2];
    }
    for (s = 0; s < 8 - 1; s++) {
        for (z = 0; z < HEIGHT_Z; z++) {
            memory[inner_sequence[s][0]][inner_sequence[s][1]][z][0] = memory[inner_sequence[s + 1][0]][inner_sequence[s + 1][1]][z][0];
            memory[inner_sequence[s][0]][inner_sequence[s][1]][z][1] = memory[inner_sequence[s + 1][0]][inner_sequence[s + 1][1]][z][1];
            memory[inner_sequence[s][0]][inner_sequence[s][1]][z][2] = memory[inner_sequence[s + 1][0]][inner_sequence[s + 1][1]][z][2];
        }
    }
    for (z = 0; z < HEIGHT_Z; z++) {
        memory[inner_sequence[7][0]][inner_sequence[7][1]][z][0] = backup[z][0];
        memory[inner_sequence[7][0]][inner_sequence[7][1]][z][1] = backup[z][1];
        memory[inner_sequence[7][0]][inner_sequence[7][1]][z][2] = backup[z][2];
    }
}

void program_plane_x_direction(u08 depth_y, s08 red, s08 green, s08 blue, u08 blackothers) {
    u08 x, y, z;
    for (x = 0; x < WIDTH_X; x++) {
        for (y = 0; y < WIDTH_Y; y++) {
            for (z = 0; z < HEIGHT_Z; z++) {
                if (y == depth_y) {
                    memory[x][y][z][0] = blue;
                    memory[x][y][z][1] = green;
                    memory[x][y][z][2] = red;
                } else {
                    if (blackothers) {
                        memory[x][y][z][0] = 0;
                        memory[x][y][z][1] = 0;
                        memory[x][y][z][2] = 0;
                    }
                }
            }
        }
    }
}

void program_plane_y_direction(u08 depth_x, s08 red, s08 green, s08 blue, u08 blackothers) {
    u08 x, y, z;
    for (x = 0; x < WIDTH_X; x++) {
        for (y = 0; y < WIDTH_Y; y++) {
            for (z = 0; z < HEIGHT_Z; z++) {
                if (x == depth_x) {
                    memory[x][y][z][0] = blue;
                    memory[x][y][z][1] = green;
                    memory[x][y][z][2] = red;
                } else {
                    if (blackothers) {
                        memory[x][y][z][0] = 0;
                        memory[x][y][z][1] = 0;
                        memory[x][y][z][2] = 0;
                    }
                }
            }
        }
    }
}

void program_plane_z_direction(u08 depth_z, s08 red, s08 green, s08 blue, u08 blackothers) {
    u08 x, y, z;
    for (x = 0; x < WIDTH_X; x++) {
        for (y = 0; y < WIDTH_Y; y++) {
            for (z = 0; z < HEIGHT_Z; z++) {
                if (z == depth_z) {
                    memory[x][y][z][0] = blue;
                    memory[x][y][z][1] = green;
                    memory[x][y][z][2] = red;
                } else {
                    if (blackothers) {
                        memory[x][y][z][0] = 0;
                        memory[x][y][z][1] = 0;
                        memory[x][y][z][2] = 0;
                    }
                }
            }
        }
    }
}

void program_small_cube(u08 topLeft_x, u08 topLeft_y, u08 topLeft_z,
        u08 bottomRight_x, u08 bottomRight_y, u08 bottomRight_z,
        s08 red, s08 green, s08 blue, u08 blackothers) {
    u08 x, y, z;
    for (x = 0; x < WIDTH_X; x++) {
        for (y = 0; y < WIDTH_Y; y++) {
            for (z = 0; z < HEIGHT_Z; z++) {
                if (topLeft_x <= x && x <= bottomRight_x
                        && topLeft_y <= y && y <= bottomRight_y
                        && topLeft_z <= z && z <= bottomRight_z) {
                    memory[x][y][z][0] = blue;
                    memory[x][y][z][1] = green;
                    memory[x][y][z][2] = red;
                } else {
                    if (blackothers) {
                        memory[x][y][z][0] = 0;
                        memory[x][y][z][1] = 0;
                        memory[x][y][z][2] = 0;
                    }
                }
            }
        }
    }
}

void pageFlip(void) {
    u08 next_buffer;
    next_buffer = (active_buffer + 1) % 2;
    memoryToBuffer(next_buffer);
    active_buffer = next_buffer;
}

int main(void) {
    cli();
    //cli();
    // start i2c communication
    //i2cInit();
    //! Set the local (AVR processor's) I2C device address
    //i2cSetLocalDeviceAddr(I2C_DEVICE_ADDR, 1);

    // Set the user function which handles receiving (incoming) data as a slave
    //i2cSetSlaveReceiveHandler(&i2c_incomming_call);
    //! Set the user function which handles transmitting (outgoing) data as a slave
    //i2cSetSlaveTransmitHandler(&i2c_incomming_request);

    // initialize the UART (serial port)
    //uartInit();
    // make all rprintf statements use uart for output
    //rprintfInit(uartSendByte);
    //rprintfProgStrM("I2C DifferentialDrive Controller\n");

    initIO();

    // initialize the timer system
    timer0Init();
    timer0SetPrescaler(TIMER_CLK_DIV8);
    timerAttach(0, &callback_timer0ovf);
    timer2Init();
    timer2SetPrescaler(TIMER_CLK_DIV64);

    timerAttach(2, &callback_timer2ovf);

    // black out all buffers
    program_solid(0, 0, 0);
    memoryToBuffer(0);
    memoryToBuffer(1);

    // ready to party
    sei();
    u08 beat = 0;


    while (1) {
        u08 rotationStep;
        u08 colorStep;
        for (colorStep = 0; colorStep < NUM_COLOR_SEQUENCE; colorStep++) {
            program_plane_y_direction(2, rgbColorSequence[colorStep][0], rgbColorSequence[colorStep][1], rgbColorSequence[colorStep][2], 1);
            for (rotationStep = 0; rotationStep < 8; rotationStep++) { // spin round once
                pageFlip();
                program_rotation_left45();
                delay_ms(50);
            }
        }

        for (colorStep = 0; colorStep < NUM_COLOR_SEQUENCE; colorStep++) {
            program_ball(rgbColorSequence[colorStep][0], rgbColorSequence[colorStep][1], rgbColorSequence[colorStep][2], 1);
            pageFlip();
            delay_ms(8 * 50);
        }

        for (colorStep = 0; colorStep < NUM_COLOR_SEQUENCE; colorStep++) {
            u08 steps[8] = {2, 3, 4, 3, 2, 1, 0, 1};
            for (rotationStep = 0; rotationStep < 8; rotationStep++) { // spin round once
                program_plane_y_direction(steps[rotationStep], rgbColorSequence[colorStep][0], rgbColorSequence[colorStep][1], rgbColorSequence[colorStep][2], 1);
                pageFlip();
                delay_ms(50);
            }
        }

        for (colorStep = 0; colorStep < NUM_COLOR_SEQUENCE; colorStep++) {
            u08 steps[8] = {2, 3, 4, 3, 2, 1, 0, 1};
            for (rotationStep = 0; rotationStep < 8; rotationStep++) { // spin round once
                program_plane_x_direction(steps[rotationStep], rgbColorSequence[colorStep][0], rgbColorSequence[colorStep][1], rgbColorSequence[colorStep][2], 1);
                pageFlip();
                delay_ms(50);
            }
        }
        for (colorStep = 0; colorStep < NUM_COLOR_SEQUENCE; colorStep++) {
            u08 steps[8] = {2, 3, 4, 3, 2, 1, 0, 1};
            for (rotationStep = 0; rotationStep < 8; rotationStep++) { // spin round once
                program_plane_z_direction(steps[rotationStep], rgbColorSequence[colorStep][0], rgbColorSequence[colorStep][1], rgbColorSequence[colorStep][2], 1);
                pageFlip();
                delay_ms(50);
            }
        }

        for (colorStep = 0; colorStep < NUM_COLOR_SEQUENCE; colorStep++) {
            program_solid(rgbColorSequence[colorStep][0], rgbColorSequence[colorStep][1], rgbColorSequence[colorStep][2]);
            pageFlip();
            delay_ms(8 * 50);
        }

        program_gradient();
        pageFlip();
        delay_ms(NUM_COLOR_SEQUENCE * 8 * 50);

        for (colorStep = 0; colorStep < NUM_COLOR_SEQUENCE; colorStep++) {

            for (rotationStep = 0; rotationStep < 2; rotationStep++) {
                program_solid(0,0,0);
                pageFlip();
                delay_ms(50);
                program_solid(NUM_GRADIENTS,NUM_GRADIENTS,NUM_GRADIENTS);
                pageFlip();
                delay_ms(50);
            }
        }

         for (colorStep = 0; colorStep < NUM_COLOR_SEQUENCE; colorStep++) {

            for (rotationStep = 0; rotationStep < 4; rotationStep++) {
                program_solid(0,0,0);
                pageFlip();
                delay_ms(25);
                program_solid(NUM_GRADIENTS,NUM_GRADIENTS,NUM_GRADIENTS);
                pageFlip();
                delay_ms(25);
            }
        }


    }
    return 0;
}




