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

/*
 * This code is written based on the information from:
 *     [AN3320] AVR318 : Dallas1-WireMaster on tinyAVR and megaAVR
 *     http://ww1.microchip.com/downloads/en/Appnotes/Dallas-1Wire-Master-on-tinyAVR-and-megaAVR-DS00003320A.pdf
 *     https://start.atmel.com/#example/Atmel%3Aavr318_dallas1wire%3A1.0.0%3A%3AApplication%3AAVR318_Dallas1Wire_Master_Polled%3A
 */


#ifndef INCLUDE_ECXX_PLATFORM_COMMON_OWIMASTER_TPP
#error "this file cannot be included directly"
#endif
#undef INCLUDE_ECXX_PLATFORM_COMMON_OWIMASTER_TPP


#ifndef ECXX_PLATFORM_COMMON_OWIMASTER_TPP
#define ECXX_PLATFORM_COMMON_OWIMASTER_TPP


//
// A simple OWI-master driver
//
template <typename ApplicationT>
inline Application_OWIMaster<ApplicationT>::Application_OWIMaster()
{}


template <typename ApplicationT>
inline Application_OWIMaster<ApplicationT>::~Application_OWIMaster()
{ end(); }


template <typename ApplicationT>
inline uint8_t Application_OWIMaster<ApplicationT>::crc8(uint8_t in, uint8_t seed) const
{
    uint8_t tmp;

    for(uint8_t i = 0; i < 8; ++i) {
        tmp = ( (seed ^ in) & 0x01 );
        if(tmp == 0) {
            seed >>= 1;
        }
        else {
            seed  ^= 0x18;
            seed >>= 1;
            seed  |= 0x80;
        }
        in >>= 1;
    }

    return seed;
}


template <typename ApplicationT>
inline uint16_t Application_OWIMaster<ApplicationT>::crc16(uint8_t in, uint16_t seed) const
{
    uint16_t tmp;

    for(uint8_t i = 0; i < 8; ++i) {
        tmp = ( (seed ^ in) & 0x01 );
        if(tmp == 0) {
            seed >>= 1;
        }
        else {
            seed  ^= 0x4002;
            seed >>= 1;
            seed  |= 0x8000;
        }
        in >>= 1;
    }

    return seed;
}


template <typename ApplicationT>
inline bool Application_OWIMaster<ApplicationT>::begin(uint8_t pin)
{
    // Ensure that this OWIMaster has been uninitialized (in case it has been initialized before)
    end();

    // Yield first
    ApplicationT::yield();

    // Ask the platform-specific's function to do the initialization (and check if there is any slave attached)
    if( !ApplicationT::owiBegin(_owiData, pin) ) return false;

    // Initialization success
    return true;
}


template <typename ApplicationT>
inline void Application_OWIMaster<ApplicationT>::end()
{
    // Simply return if this OWIMaster has never been initialized before
    if( !_owiData.valid() ) return;

    // Ask the platform-specific's function to do the uninitialization
    ApplicationT::owiEnd(_owiData);
}


template <typename ApplicationT>
inline bool Application_OWIMaster<ApplicationT>::reset() const
{
    // Simply return if this OWIMaster has never been initialized before
    if( !_owiData.valid() ) return false;

    // Send reset
    return ApplicationT::owiReset(_owiData);
}


template <typename ApplicationT>
inline bool Application_OWIMaster<ApplicationT>::write(uint8_t value) const
{
    // Simply return if this OWIMaster has never been initialized before
    if( !_owiData.valid() ) return false;

    // Yield first
    ApplicationT::yield();

    // Write the bits
    for(uint8_t i = 0; i < 8; ++i) {
        if(value & 0x01) ApplicationT::owiWriteBit1(_owiData);
        else             ApplicationT::owiWriteBit0(_owiData);
        value >>= 1;
    }

    // Done
    return true;
}


template <typename ApplicationT>
inline bool Application_OWIMaster<ApplicationT>::read(uint8_t& value) const
{
    // Simply return if this OWIMaster has never been initialized before
    if( !_owiData.valid() ) return false;

    // Yield first
    ApplicationT::yield();

    // Set to zero first
    value = 0;

    // Read the bits
    for(uint8_t i = 0; i < 8; ++i) {
        value >>= 1;
        if( ApplicationT::owiReadBit(_owiData) ) value |= 0x80;
    }

    // Done
    return true;
}


template <typename ApplicationT>
inline uint8_t Application_OWIMaster<ApplicationT>::read() const
{
    // Simply return if this OWIMaster has never been initialized before
    if( !_owiData.valid() ) return 0;

    // Yield first
    ApplicationT::yield();

    // Set to zero first
    uint8_t value = 0;

    // Read the bits
    for(uint8_t i = 0; i < 8; ++i) {
        value >>= 1;
        if( ApplicationT::owiReadBit(_owiData) ) value |= 0x80;
    }

    // Done
    return value;
}


template <typename ApplicationT>
inline void Application_OWIMaster<ApplicationT>::skipROM() const
{ write(cmdSkip); }


template <typename ApplicationT>
inline void Application_OWIMaster<ApplicationT>::matchROM(const uint8_t* values8) const
{
     write(cmdMatch);
     for(uint8_t i = 0; i < 8; ++i) write(*values8++);
}


template <typename ApplicationT>
inline void Application_OWIMaster<ApplicationT>::readROM(uint8_t* values8) const
{
     write(cmdRead);
     for(uint8_t i = 0; i < 8; ++i) read(*values8++);
}


template <typename ApplicationT>
inline void Application_OWIMaster<ApplicationT>::matchROM(const uint8_t (&value)[8]) const
{ matchROM(&value[0]); }


template <typename ApplicationT>
inline void Application_OWIMaster<ApplicationT>::readROM(uint8_t (&value)[8]) const
{ readROM(&value[0]); }


template <typename ApplicationT>
inline uint8_t Application_OWIMaster<ApplicationT>::searchROM(uint8_t (&bitPattern_)[8], uint8_t lastDeviation) const
{
    // Send the search command
    write(cmdSearch);

    // Working variables
    uint8_t* bitPattern   = &bitPattern_[0];
    uint8_t  newDeviation = 0;
    uint8_t  currentBit   = 1;
    uint8_t  bitMask      = 0x01;

    // Walk through all 64 bits
    while(currentBit <= 64) {
        // Read bit from the bus twice
        const bool bitA = ApplicationT::owiReadBit(_owiData);
        const bool bitB = ApplicationT::owiReadBit(_owiData);
        // Error if both bits are '1'
        if(bitA && bitB) return Invalid;
        // Bit A and B are different (all slaves have the same bit as this position)
        if(bitA ^ bitB) {
            // Set the bit in bitPattern to this value.
            if(bitA) (*bitPattern) |=  bitMask;
            else     (*bitPattern) &= ~bitMask;
        }
        // Both bits are '0'
        else {
            // If this is where a choice was made the last time, a '1' bit is selected this time
            if(currentBit == lastDeviation) {
                (*bitPattern) |= bitMask;
            }
            // For the rest of the ID, '0' bits are selected when discrepancies occur
            else if(currentBit > lastDeviation) {
                (*bitPattern) &= ~bitMask;
                newDeviation   = currentBit;
            }
            // If current bit in bit pattern is '0', then this is the new deviation
            else if( !(*bitPattern & bitMask) ) {
                newDeviation = currentBit;
            }
            // If the bit is already '1', do nothing
            else {
            }
        }
        // Send the selected bit to the bus
        if( (*bitPattern) & bitMask )
            ApplicationT::owiWriteBit1(_owiData);
        else
            ApplicationT::owiWriteBit0(_owiData);
        // Increment the bit counter
        ++currentBit;
        // Adjust bit mask and bit pattern
        bitMask <<= 1;
        if(!bitMask) {
            bitMask = 0x01;
            ++bitPattern;
        }
    } // while currentBit

    // Return the new deviation
    return newDeviation;
}


template <typename ApplicationT> template <uint8_t N>
inline uint8_t Application_OWIMaster<ApplicationT>::searchBus(OneWireDevice (&slaveDevice)[N]) const
{
    // Initialize all slave addresses to zeroes
    for(uint8_t i = 0; i < N; ++i) {
        for(uint8_t j = 0; j < 8; ++j) {
            slaveDevice[i].id[j] = 0;
        }
    }

    // Search for slave devices
    uint8_t lastDeviation = 0;
    uint8_t numSlaves     = 0;
    do {
        lastDeviation = searchROM(slaveDevice[numSlaves].id, lastDeviation);
        if(lastDeviation == Invalid) return Invalid;
        ++numSlaves;
    } while(lastDeviation != 0);


    // Perform CRC check
    for(uint8_t i = 0; i < numSlaves; ++i) {
        if( !crcROM(slaveDevice[i].id) ) return 0;
    }

    // Done
    return numSlaves;
}


template <typename ApplicationT> template <uint8_t N>
inline uint8_t Application_OWIMaster<ApplicationT>::findFamily(const OneWireDevice (&slaveDevice)[N], uint8_t familyID, uint8_t startOffset) const
{
    for(uint8_t i = startOffset; i < N; ++i) {
        if(slaveDevice[i].id[0] == familyID) return i;
    }

    return Invalid;
}


template <typename ApplicationT> template <uint8_t N, uint8_t F>
inline uint8_t Application_OWIMaster<ApplicationT>::findFamily(const OneWireDevice (&slaveDevice)[N], const uint8_t (&familyID)[F], uint8_t startOffset) const
{
    for(uint8_t i = startOffset; i < N; ++i) {
        for(uint8_t j = 0; j < F; ++j) {
            if(slaveDevice[i].id[0] == familyID[j]) return i;
        }
    }

    return Invalid;
}


template <typename ApplicationT>
inline bool Application_OWIMaster<ApplicationT>::crcROM(const uint8_t (&romValue)[8]) const
{
    uint8_t crc8val = 0;

    for(uint8_t i = 0; i < 7; ++i) {
        crc8val = crc8(romValue[i], crc8val);
    }

    return crc8val == romValue[7];
}


#endif
