//Source: https://github.com/rambo/pca9635

#include "pca9635.h"

// Constructor
pca9635::pca9635() {
    this->device_address = 0x70; // Default to the all-call address
    this->autoincrement_bits = 0x80; // Autoincrement all
    this->available = false;
    for (uint8_t i = 0; i < NUM_CHANNELS; i++) {
    	this->pwm_values[i] = 0;
    }
	for (uint8_t i = 0; i < MODE_REGISTERS; i++) {
    	this->led_mode_regs[i] = 0;
    }
}

void pca9635::begin(byte dev_addr, boolean wire_begin, boolean init)
{
	device_address = dev_addr;
	if (wire_begin) {
		if (!Wire.begin(21, 22, 400000UL)) {
			Serial.println("Error while setup i2c bus!");
		}
		this->reset();
	}
    if (init)
    {
        if (this->set_sleep(0x0)) { // Wake up oscillators
        	this->available = true;
        	delayMicroseconds(500); // Wait for the oscillators to stabilize
        	this->set_led_mode(0); // Default to full PWM mode for all drivers
        	this->set_invert_output(1); //invert output channels to use with external nfet
        	this->set_output_not_active_state(0x00); //LEDn=0 if OE is not set
        	this->set_group_control(1); // setup group blinking
        	this->set_group_dimming(0x80); // set duty cycle to 50%
//			this->set_driver_mode(0x00); // set to open-drain
        } else {
        	this->available = false;
        }
    }
}

void pca9635::begin(byte dev_addr, boolean wire_begin)
{
    pca9635::begin(dev_addr, wire_begin, false);
}

boolean pca9635::readSingleReg(uint8_t reg, uint8_t *data) {

    Wire.beginTransmission(this->device_address);
    if (Wire.write(reg | autoincrement_bits) != 1) return false;
    if (Wire.endTransmission(false)) return false; //send restart
    if (Wire.requestFrom((uint16_t) this->device_address,1U,true) == 1U) { // read one byte, then send stop
    	*data = Wire.read();
    	return true;
    }
	return false;
}

boolean pca9635::writeSingleReg(uint8_t reg, uint8_t data) {
    return this->writeManyReg(reg, 1, &data);
    
/*    Wire.beginTransmission(this->device_address);
    if (Wire.write(reg | autoincrement_bits) != 1) return false;
    if (Wire.write(data) != 1) return false;
    if (!Wire.endTransmission(true)) { //send stop
    	return true;
    }
	return false;
	*/
}

boolean pca9635::writeManyReg(uint8_t reg, uint8_t cnt, uint8_t *data) {
    Wire.beginTransmission(this->device_address);
    if (Wire.write(reg | autoincrement_bits) != 1) return false;
    if (Wire.write(data, cnt) != cnt) return false;
    if (!Wire.endTransmission(true)) { //send stop
    	return true;
    }
	return false;
}


void pca9635::exchangeValueReg(uint8_t *reg, uint8_t mask, uint8_t value) {
	*reg = (*reg & mask) | value;
}

boolean pca9635::readModifyWriteReg(uint8_t reg, uint8_t mask, uint8_t value) {
	uint8_t tmp;
    if (!this->readSingleReg(reg, &tmp)) {
    	Serial.println("error while reading single reg");
    	return false;
    }
    this->exchangeValueReg(&tmp, mask, value);
    return this->writeSingleReg(reg, tmp);
}

/**
 * Control the led channel mode, modes: 
 * 0=fully off
 * 1=fully on (no PWM)
 * 2=individual PWM only
 * 3=individual and group PWM
 *
 * Remember that led numbers start from 0
 */
boolean pca9635::set_led_mode(byte ledno, byte mode)
{
    byte reg = 0x17;
    if ( ledno >= 8 && ledno < 11)
    {
        reg = 0x16;
    }
    if ( ledno >= 4 && ledno < 8)
    {
        reg = 0x15;
    }
    if (ledno < 4)
    {
        reg = 0x14;
    }
    byte value = 0;
    switch (mode)
    {
        case 0:
            value = B00000000;
            break;
        case 1:
            value = B01010101;
            break;
        case 2:
            value = B10101010;
            break;
        case 3:
            value = B11111111;
            break;
    }
    byte mask = B00000000;
    switch(ledno%4)
    {
        case 0:
            mask = (byte)~B00000011;
            break;
        case 1:
            mask = (byte)~B00001100;
            break;
        case 2:
            mask = (byte)~B00110000;
            break;
        case 3:
            mask = (byte)~B11000000;
            break;
	} 
    this->exchangeValueReg(&this->led_mode_regs[ledno/4], mask, value);
    return this->readModifyWriteReg(reg | autoincrement_bits, mask, value); 
}

/**
 * Set mode for all leds 
 * 0=fully off
 * 1=fully on (no PWM)
 * 2=individual PWM only
 * 3=individual and group PWM
 */
boolean pca9635::set_led_mode(byte mode)
{
    byte value;
    switch (mode)
    {
        case 0:
            value = B00000000;
            break;
        case 1:
            value = B01010101;
            break;
        case 2:
            value = B10101010;
            break;
        case 3:
            value = B11111111;
            break;
        default:
            value = 0;
            break;
    }
    for (uint8_t i = 0; i < MODE_REGISTERS; i++) {
    	this->led_mode_regs[i] = value;
    }
    return this->writeManyReg(0x14 | autoincrement_bits, 4, this->led_mode_regs);
}

/**
 * Set modes to given leds
 * leds specified in the 'leds'-array
 * modes specified in the 'modes'-array
 */
boolean pca9635::set_all_led_modes(uint8_t cnt, uint8_t leds[], uint8_t modes[]) {
	for (uint8_t i = 0; i < cnt; i++) {
		uint8_t mode = modes[i];
		if (mode > 3) {
			return false;
		}
		uint8_t ledno = leds[i];
		if (ledno >= NUM_CHANNELS) {
			return false;
		}
		this->exchangeValueReg(&this->led_mode_regs[ledno/4], ~(0x3<<(2*(ledno%4))), mode<<(2*(ledno%4)));
	}
	return this->writeManyReg(0x14 | autoincrement_bits, 4, this->led_mode_regs);
}

/**
 * Enable given SUBADDRess (1-3)
 */
boolean pca9635::enable_subaddr(byte addr)
{
    byte value;
    switch (addr)
    {
        case 1:
            value = _BV(3); // 0x71
            break;
        case 2:
            value = _BV(2); // 0x72
            break;
        case 3:
            value = _BV(1); // 0x74
            break;
        default:
            value = 0;
            break;
    }
    byte mask = ~value;
    return this->readModifyWriteReg(0x0 | autoincrement_bits, mask, value);
}

/**
 * Changes the driver mode between open drain(0x0) and totem-pole (0x1, the default)
 */
boolean pca9635::set_driver_mode(byte mode)
{
    return this->readModifyWriteReg(0x01 | autoincrement_bits, (byte)~_BV(2), (mode & 0x01) << 2);
}


/**
 * Changes the driver mode between not inverted(0x0, the default) and inverted (0x1)
 */
boolean pca9635::set_invert_output(byte mode)
{
    return this->readModifyWriteReg(0x01 | autoincrement_bits, (byte)~_BV(4), (mode & 0x01) << 4);
}


/**
 * Changes the driver inactive state: LEDn = 0 (0x00), LEDn = 1 if driver_mode = 1 or LEDn = high-impedance if driver_mode = 0 (0x01, the default) or LEDn = high-impedance (0x10)
 */
boolean pca9635::set_output_not_active_state(byte mode)
{
	if (mode > 2) {
		return false;
	}
    return this->readModifyWriteReg(0x01 | autoincrement_bits, (byte)~(_BV(0) | _BV(1)), (mode & 0x03));
}


/**
 * Changes the group control mode between dimming(0x0, the default) and blinking (0x1)
 */
boolean pca9635::set_group_control(byte mode)
{
	
    return this->readModifyWriteReg(0x01 | autoincrement_bits, (byte)~_BV(5), (mode & 0x01) << 5);
}

/**
 * Changes the oscillator mode between sleep (0x1, the default) and active (0x0)
 */
boolean pca9635::set_sleep(byte sleep)
{
    return this->readModifyWriteReg(0x00 | autoincrement_bits, (byte)~_BV(4), (sleep & 0x01) << 4);
}


/**
 * Sets the pwm value for given led, note that it must have previously been enabled for PWM control with set_mode
 * 
 * Remember that led numbers start from 0
 */
boolean pca9635::set_led_pwm(byte ledno, byte cycle) {
	byte reg = 0x02 + ledno;
	boolean ret = this->writeManyReg(reg | autoincrement_bits, 1, &cycle);
	if (ret) {
		this->pwm_values[ledno] = cycle;
	}
	return ret;
}


/**
 * Sets the pwm values for given leds, start led and the cnt of leds to change, note that it must have previously been enabled for PWM control with set_mode
 * the value for each led has to be set in the array.
 *
 * Remember that led numbers start from 0
 */
boolean pca9635::set_all_led_pwm(uint8_t start_led, uint8_t cnt, uint8_t *cycles) {
	if (start_led >= NUM_CHANNELS || (start_led+cnt) >= NUM_CHANNELS) {
		return false;
	}
	boolean ret = this->writeManyReg((start_led + 0x02) | autoincrement_bits, cnt, cycles);
	if (ret) {
		for (uint8_t i = 0; i < cnt; i++) {
			pwm_values[i+start_led] = *(cycles+i);
		}
	}
	return ret;
}

/**
 * Sets the Group pwm value, note that it must have previously been enabled for PWM control and group PWM with set_mode
 */
boolean pca9635::set_group_dimming(uint8_t cycle) {
	return this->writeManyReg(0x12 | autoincrement_bits, 1, &cycle);
}


/**
 * Sets the Group blinking frequency, note that it must have previously been enabled for PWM control and group PWM with set_mode
 */
boolean pca9635::set_group_blinking(uint8_t freq) {
	return this->writeManyReg(0x13 | autoincrement_bits, 1, &freq);
}


/**
 * Do the software-reset song-and-dance, this should reset all drivers on the bus
 */
boolean pca9635::reset(uint8_t addr) {
#ifdef I2C_DEVICE_DEBUG
    Serial.println(F("pca9635::reset() called"));
#endif
	Wire.beginTransmission(addr);
	uint8_t byteArray[2] = {0xA5, 0x5A};
	if (Wire.write(byteArray, 2) != 2) return false;

    byte result = Wire.endTransmission(true);
    if (result > 0)
    {
#ifdef I2C_DEVICE_DEBUG
        Serial.print(F("FAILED: Wire.write(0x03, 0x5a, 0xa5); returned: "));
        Serial.println(result, DEC);
#endif
        return false;
    }
    delayMicroseconds(5); // Wait for the reset to complete
    return true;
}


boolean pca9635::reset() {
	return this->reset(0x03);
}


boolean pca9635::isAvailable() {
	return this->available;
}

/**
 * return the current configured pwm value for the selected channel
 */
uint8_t pca9635::getPWMValue(uint8_t channel) {
	if (channel < NUM_CHANNELS) {
		return this->pwm_values[channel];
	} else {
		return 0;
	}
}

// Instance for the all-call address
pca9635 PCA9635 = pca9635();
