#include "mbed.h"
#include "main.h"
#include "pca9685.h"

DigitalOut PCA9685_PWM_nEN(PA_7);
DigitalOut PCA9685_VIN_EN(PB_1);
AnalogOut  PCA9685_VIN(PA_5);

void PCA9685_Init(void) {
    PCA9685_REGS regs = {0};
    
    // Power up PCA9685 at full voltage with outputs disabled
    PCA9685_VIN.write(1);
    PCA9685_VIN_EN.write(1);
    PCA9685_PWM_nEN.write(1);

    regs.MODE1.AI = 1;         // Turn on autoincrement
    regs.MODE1.SLEEP = 1;      // Start disabled
    regs.MODE1.ALLCALL = 1;    // Enable response to all call address
    
    regs.MODE2.OUTDRV = 1;     // Configure output for totem pole drive
    
    I2C_Write(PCA9685_All_Call, FIELD_OFFSET(PCA9685_REGS, MODE1), 
        regs.AS_BYTE + FIELD_OFFSET(PCA9685_REGS, MODE1),
        FIELD_SIZE_THROUGH(PCA9685_REGS, MODE1, MODE2));
    
    regs.PRE_SCALE = 0x03;      // Set PWM frequency to 1526Hz

    I2C_Write(PCA9685_All_Call, FIELD_OFFSET(PCA9685_REGS, PRE_SCALE), 
        regs.AS_BYTE + FIELD_OFFSET(PCA9685_REGS, PRE_SCALE),
        FIELD_SIZE_THROUGH(PCA9685_REGS, PRE_SCALE, PRE_SCALE));
    
    PCA9685_SetDot(0);
    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 10; j++) {
            PCA9685_SetDigit(i, j, 0);
        }
    }
    
    regs.MODE1.SLEEP = 0;       // Re-enable outputs
    
    I2C_Write(PCA9685_All_Call, FIELD_OFFSET(PCA9685_REGS, MODE1), 
        regs.AS_BYTE + FIELD_OFFSET(PCA9685_REGS, MODE1),
        FIELD_SIZE_THROUGH(PCA9685_REGS, MODE1, MODE1));

    // Enable outputs after configuration has been completed
    PCA9685_PWM_nEN.write(0);
}

void PCA9685_SetVoltage(float Percent) {
    Percent = Percent * 0.6;
    PCA9685_VIN.write(Percent);
}

void PCA9685_SetDigit(int Tube, int Digit, int Brightness) {
    LED_CTRL reg = {0};

    if (Brightness >= PCA9685_Max_Brightness) {
        reg.ON_FULL = 1;
    } else if (Brightness == 0) {
        reg.OFF_FULL = 1;
    } else {
        reg.OFF = Brightness;
    }

    I2C_Write(Tube_Mapping[Tube][Digit][MAP_ADDR],
        FIELD_OFFSET(PCA9685_REGS, LED0) + (Tube_Mapping[Tube][Digit][MAP_PIN] * sizeof(LED_CTRL)),
        reg.AS_BYTE, sizeof(LED_CTRL));
}

void PCA9685_SetDigitPwm(int Tube, int Digit, int StartPwm, int EndPwm) {
    LED_CTRL reg = {0};

    reg.ON = StartPwm;
    reg.OFF = EndPwm;
    if (EndPwm - StartPwm >= PCA9685_Max_Brightness) {
        reg.ON_FULL = 1;
    } else if (EndPwm == 0) {
        reg.OFF_FULL = 1;
    }

    I2C_Write(Tube_Mapping[Tube][Digit][MAP_ADDR],
        FIELD_OFFSET(PCA9685_REGS, LED0) + (Tube_Mapping[Tube][Digit][MAP_PIN] * sizeof(LED_CTRL)),
        reg.AS_BYTE, sizeof(LED_CTRL));
}

void PCA9685_SetDot(int Brightness) {
    LED_CTRL reg = {0};

    if (Brightness >= PCA9685_Max_Brightness) {
        reg.ON_FULL = 1;
    } else if (Brightness == 0) {
        reg.OFF_FULL = 1;
    } else {
        reg.OFF = Brightness;
    }

    I2C_Write(TUBE_DOT_ADDR, FIELD_OFFSET(PCA9685_REGS, TUBE_DOT_PIN), reg.AS_BYTE, sizeof(LED_CTRL));
}

void PCA9685_SetDotPwm(int StartPwm, int EndPwm) {
    LED_CTRL reg = {0};

    reg.ON = StartPwm;
    reg.OFF = EndPwm;
    if (EndPwm - StartPwm >= PCA9685_Max_Brightness) {
        reg.ON_FULL = 1;
    } else if (EndPwm == 0) {
        reg.OFF_FULL = 1;
    } else if (EndPwm >= PCA9685_Max_Brightness) {
        reg.OFF = 0xFFF;
    }

    I2C_Write(TUBE_DOT_ADDR, FIELD_OFFSET(PCA9685_REGS, TUBE_DOT_PIN), reg.AS_BYTE, sizeof(LED_CTRL));
}

void PCA9685_EnableOutput(bool Enabled) {
    if (Enabled) {
        PCA9685_PWM_nEN.write(0);
    } else {
        PCA9685_PWM_nEN.write(1);
    }
}
