/*
 * Copyright (C) 2020-2021 Aloysius Indrayanto
 *
 * This file is part of the eCxx library, see LICENSE file for the license details.
 */


#include <eCxx/Platform/CharacterLCD_4Bit.h>


namespace eCxx {


//
// A simple character LCD driver using 4-bit parallel output method
//
void CharacterLCD_4Bit::setBacklight(bool on, bool immediate)
{
    // Do not proceed if it has not been initialized
    if(!_initialized) return;

    // Store the backlight state, depending on the active mode
    _blState = (on && _blActvHigh) || (!on && !_blActvHigh);

    // Set it immediately as needed
    if(immediate) {
        // Do not use pulseData() here because it will assert the EN pin
        Application::digitalWrite(_pinBL, _blState);
    }
}


void CharacterLCD_4Bit::sendLNibble(uint8_t value) const noexcept
{
    // Do not proceed if it has not been initialized
    if(!_initialized) return;

    // Send the low nibble only
    pulseData(value & 0x0F, false);
}


void CharacterLCD_4Bit::sendCommand(uint8_t value) const noexcept
{
    // Write   -> RW = 0
    // Command -> RS = 0

    // Do not proceed if it has not been initialized
    if(!_initialized) return;

    // Send the high nibble followed by the low nibble
    pulseData(value >> 4,    false);
    pulseData(value &  0x0F, false);
}


void CharacterLCD_4Bit::sendData(uint8_t value) const noexcept
{
    // Write -> RW = 0
    // Data  -> RS = 1

    // Do not proceed if it has not been initialized
    if(!_initialized) return;

    // Send the high nibble followed by the low nibble
    pulseData(value >> 4,    true);
    pulseData(value &  0x0F, true);
}


//
// Private member functions
//
void CharacterLCD_4Bit::init()
{
    // Do not proceed if it has already been initialized
    if(_initialized) return;

    // Set flag
    _initialized = true;

    // Configure the RW pin and immediately set it to low
    Application::setPinMode  (_pinRW, Application::PinMode::Output);
    Application::digitalWrite(_pinRW, false                       );

    // Configure the EN pin and immediately set it to low
    Application::setPinMode  (_pinEN, Application::PinMode::Output);
    Application::digitalWrite(_pinEN, false                       );

    // Configure the RS pin and immediately set it to high
    Application::setPinMode  (_pinRS, Application::PinMode::Output);
    Application::digitalWrite(_pinRS, true                        );

    // Configure the BL pin and immediately turn it on
    Application::setPinMode(_pinBL, Application::PinMode::Output);
    setBacklight           (true, true);

    // COnfigure the D[4:7] pin and immediately set them to low
    Application::setPinMode  (_pinD4, Application::PinMode::Output);
    Application::setPinMode  (_pinD5, Application::PinMode::Output);
    Application::setPinMode  (_pinD6, Application::PinMode::Output);
    Application::setPinMode  (_pinD7, Application::PinMode::Output);

    Application::digitalWrite(_pinD4, false                       );
    Application::digitalWrite(_pinD5, false                       );
    Application::digitalWrite(_pinD6, false                       );
    Application::digitalWrite(_pinD7, false                       );
}


void CharacterLCD_4Bit::uninit()
{
    // Do not proceed if it has never been initialized before
    if(!_initialized) return;

    // Turn off backlight and set the EN pin to low
    setBacklight             (false,  true );
    Application::digitalWrite(_pinEN, false);

    // Reset flag
    _initialized = false;

    // Configure all pins to input
    Application::setPinMode  (_pinD4, Application::PinMode::Input);
    Application::setPinMode  (_pinD5, Application::PinMode::Input);
    Application::setPinMode  (_pinD6, Application::PinMode::Input);
    Application::setPinMode  (_pinD7, Application::PinMode::Input);

    Application::setPinMode  (_pinBL, Application::PinMode::Input);

    Application::setPinMode  (_pinRS, Application::PinMode::Input);
    Application::setPinMode  (_pinEN, Application::PinMode::Input);
    Application::setPinMode  (_pinRW, Application::PinMode::Input);
}


void CharacterLCD_4Bit::pulseData(uint8_t value, bool rs) const noexcept
{
    // The RW pin is already low due to init()

                   Application::digitalWrite(_pinD4, value & 0b00000001);
                   Application::digitalWrite(_pinD5, value & 0b00000010);
                   Application::digitalWrite(_pinD6, value & 0b00000100);
                   Application::digitalWrite(_pinD7, value & 0b00001000);

                   Application::digitalWrite(_pinRS, rs                );

                   Application::digitalWrite(_pinBL, _blState          );

    if(_useRWAsE2) Application::digitalWrite(_pinRW, true              );
    else           Application::digitalWrite(_pinEN, true              );

                   Application::delayUS     (25                        );

    if(_useRWAsE2) Application::digitalWrite(_pinRW, false             );
    else           Application::digitalWrite(_pinEN, false             );
}


} // namespace eCxx
