#include "./CaiXianlin.h"

uint16_t toLucal(uint16_t n, bool parityInverted) {
    uint16_t gray = n ^ (n >> 1);
    uint16_t lucal = gray << 1;

    // Calculate parity
    gray ^= gray >> 8;
    gray ^= gray >> 4;
    gray ^= gray >> 2;
    gray ^= gray >> 1;
    gray &= 1;
    // gray now contains the parity

    // flip gray if partityInverted is true
    gray ^= parityInverted;
    // Add the partiy bit
    lucal |= gray;

    return lucal;
}

uint64_t toData(uint16_t transmitterID, uint8_t channel, CaiXianlin::MessageType command, uint8_t intensity, bool lucalEncode) {
    uint64_t data = 0;

    // Calculate checksum
    data |= transmitterID & 0xFFFF;

    data <<= 4;
    data |= channel & 0xF;

    data <<= 4;
    data |= command & 0xF;

    data <<= 8;
    data |= intensity & 0xFF;

    uint8_t checksum = 0;
    checksum += data & 0xFF;
    checksum += (data & 0xFF00) >> 8;
    checksum += (data & 0xFF0000) >> 16;
    checksum += (data & 0xFF000000) >> 24;

    // Create data packet and (optionally) lucal encode

    data = 0;
    data |= (lucalEncode ? toLucal(transmitterID, false) : transmitterID) & 0xFFFF;

    data <<= 4;
    data |= (lucalEncode ? toLucal(channel, false) : channel) & 0xF;

    data <<= 4;
    data |= (lucalEncode ? toLucal(command, false) : command) & 0xF;

    data <<= 8;
    data |= (lucalEncode ? toLucal(intensity, true) : intensity) & 0xFF;

    data <<= 8;
    data |= (lucalEncode ? toLucal(checksum, false) : checksum) & 0xFF;

    data <<= 2;

    return data;
}

namespace CaiXianlin {
    Transmitter::Transmitter(int pin) {
        _pin = pin;
        _lastState = false;
        pinMode(_pin, OUTPUT);
    }

    void Transmitter::_toggle() {
        _lastState = !_lastState;
        digitalWrite(_pin, _lastState);
    }

    // Encodes all fields, then runs the next transmit() function with generated data
    void Transmitter::transmit(uint16_t transmitterID, uint8_t channel, CaiXianlin::MessageType command, uint8_t intensity, bool lucalEncode) {
        this->transmit(toData(transmitterID, channel, command, intensity, lucalEncode));
    }

    // Transmits the first 43 bits of 'data'
    void Transmitter::transmit(uint64_t data) {
        bool invert = false;
        for (int i = 0; i < 43; i++) {
            switch (data >> (42-i) & 1) {
                case true:
                    _pulses[i*2] = true ^ invert;
                    _pulses[i*2+1] = true ^ invert;
                    invert = !invert;
                    break;
                case false:
                    _pulses[i*2] = true ^ invert;
                    _pulses[i*2+1] = false ^ invert;
                    break;
            }
        }

        for (int i = 0; i < 20; i++) {
            _toggle();
            delayMicroseconds(CAIXIANLIN_SYNC_TIME);

            for (int i2 = 0; i2 < 86; i2++) {
                _toggle();
                delayMicroseconds(_pulses[i2] ? CAIXIANLIN_LONG_TIME : CAIXIANLIN_SHORT_TIME);
            }

            _toggle();
            delayMicroseconds(CAIXIANLIN_SYNC_TIME);
        }
    }
}