/*!
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright(c) 2009 Apogee Instruments, Inc.
* \class AltaEthernetIo
* \brief First generation implemenation (alta) of the ethernet interface
*
*/

#include "AltaEthernetIo.h"

#include <sstream>
#include <iomanip>
#include <cstring>  //for memset

#include "libCurlWrap.h"
#include "apgHelper.h"
#include "ApgLogger.h"
#include "helpers.h"
#include "CamHelpers.h"

namespace
{
const int32_t MAX_WRITES_PER_URL = 40;
const int32_t MAX_READS_PER_URL = 40;

// GET    PORT      STR
std::string GetPortStr( const uint16_t PortId )
{
    std::string result;

    switch( PortId )
    {
        case 0:
            result.append("A");
            break;

        case 1:
            result.append("B");
            break;

        default:
        {
            std::stringstream errMsg;
            errMsg << "Invalid port " << PortId;
            apgHelper::throwRuntimeException( __FILE__, errMsg.str(),
                                              __LINE__, Apg::ErrorType_InvalidUsage );
        }
        break;
    }

    return result;
}

//      UINT32    TO      STR
std::string uint32ToStr( const uint32_t value )
{
    std::stringstream ss;
    ss << value;
    return ss.str();
}

//  FIND     VAL   IN     MAP
uint16_t FindValInMap(  const uint16_t Reg,
                        const std::map<uint16_t, uint16_t> &theMap )
{
    std::map<uint16_t, uint16_t>::const_iterator iter =
        theMap.find( Reg );

    if( iter != theMap.end() )
    {
        //found a value before we reached the end
        return (*iter).second;
    }
    else
    {
        std::stringstream errMsg;
        errMsg << "Failed to find register " << Reg << " in status map";
        apgHelper::throwRuntimeException( __FILE__, errMsg.str(),
                                          __LINE__, Apg::ErrorType_InvalidUsage );
        return 0;
    }

}

//  MK   HEX   REG      STR
std::string mkHexRegStr( const uint16_t Reg )
{
    if( !Reg )
    {
        std::string result("0x0000");
        return result;
    }
    else
    {
        std::stringstream ss;
        ss <<  std::hex << std::setw(6) << std::setfill('0') << std::internal << std::showbase << Reg;
        return ss.str();
    }
}
}

////////////////////////////
// CTOR
AltaEthernetIo::AltaEthernetIo( const std::string url ) : m_url( url ),
    m_fileName( __BASE_FILE__ )

{
    //open a session with the camera
    OpenSession();

    m_StatusRegs.push_back(CameraRegs::TEMP_DRIVE);
    m_StatusRegs.push_back(CameraRegs::INPUT_VOLTAGE);
    m_StatusRegs.push_back(CameraRegs::SEQUENCE_COUNTER);
    m_StatusRegs.push_back(CameraRegs::STATUS);
    m_StatusRegs.push_back(CameraRegs::TDI_COUNTER);
    m_StatusRegs.push_back(CameraRegs::TEMP_CCD);
    m_StatusRegs.push_back(CameraRegs::TEMP_HEATSINK);
}

////////////////////////////
// DTOR
AltaEthernetIo::~AltaEthernetIo()
{
    CloseSession();
}

////////////////////////////
//  OPEN    SESSION
void AltaEthernetIo::OpenSession()
{
    const std::string fullUrl = m_url + "/SESSION?Open";

    CLibCurlWrap theCurl;

    std::string result;
    theCurl.HttpGet( fullUrl, result );

    if( std::string::npos == result.find("SessionId=") )
    {
        std::string errMsg = "Invalid open session response = " + result;
        apgHelper::throwRuntimeException( m_fileName, errMsg,
                                          __LINE__, Apg::ErrorType_Critical  );
    }

    //log device we are connected to
    const std::string msg = "Connected to device " + m_url;
    ApgLogger::Instance().Write(ApgLogger::LEVEL_RELEASE, "info",
                                msg );
}

////////////////////////////
//  CLOSE    SESSION
void AltaEthernetIo::CloseSession()
{
    const std::string fullUrl = m_url + "/SESSION?Close";

    CLibCurlWrap theCurl;

    std::string result;
    theCurl.HttpGet( fullUrl, result );

    if( std::string::npos == result.find("SessionId=") )
    {
        //no throwing b/c this is called by the dtor
        //log the error
        std::string errMsg = "Invalid close session response = " + result;
        ApgLogger::Instance().Write(ApgLogger::LEVEL_RELEASE, "error", errMsg);
    }

    //log what device we have disconnected from
    const std::string msg = "Connection to device " + m_url + " is closed.";
    ApgLogger::Instance().Write(ApgLogger::LEVEL_RELEASE, "info",
                                msg );

}

////////////////////////////
// READ     REG
uint16_t AltaEthernetIo::ReadReg( uint16_t reg ) const
{

    const std::string finalUrl = m_url + "/FPGA?RR=" + help::uShort2Str( reg );

    CLibCurlWrap theCurl;
    std::string result;
    theCurl.HttpGet( finalUrl, result );

    std::vector<std::string> tokens = help::MakeTokens(result, "=");

    uint16_t FpgaData = 0;
    std::istringstream is( tokens[1] );
    is >> std::hex >> FpgaData;

    return FpgaData;
}

////////////////////////////
// READ     REGS
std::map<uint16_t, uint16_t> AltaEthernetIo::ReadRegs(const std::vector<uint16_t> &Regs )
{
    //build the get string with all of the registers
    std::string base = m_url + "/FPGA?";
    std::string finalUrl = base;

    std::vector<uint16_t>::const_iterator iter;

    bool first = true;
    std::string finalResult;
    int32_t count = 0;

    for( iter = Regs.begin(); iter != Regs.end(); ++iter )
    {
        if( first )
        {
            std::string vv = "RR=" +  help::uShort2Str( *iter );
            finalUrl.append(vv);
            first = false;
        }
        else
        {
            std::string vv = "&RR=" +  help::uShort2Str( *iter );
            finalUrl.append(vv);
        }

        if( MAX_READS_PER_URL - 1 == count )
        {
            //send the max data
            CLibCurlWrap theCurl;
            std::string result;
            theCurl.HttpGet( finalUrl, result );
            finalResult.append( result );

            //reset
            count = 0;
            finalUrl = base;
            first = true;
        }
        else
        {
            ++count;
        }

    }

    if( count )
    {
        //send the cmd
        CLibCurlWrap theCurl;
        std::string result;
        theCurl.HttpGet( finalUrl, result );
        finalResult.append( result );
    }

    //convert the string into a map
    std::vector<std::string> tokens = help::MakeTokens(finalResult, "\n");

    std::vector<std::string>::iterator siter;

    std::map<uint16_t, uint16_t> mapOut;

    for( siter = tokens.begin(); siter != tokens.end(); ++siter )
    {
        if( (*siter).empty() )
        {
            continue;
        }

        int32_t regStart = apgHelper::SizeT2Int32( (*siter).find("[") );
        int32_t regEnd = apgHelper::SizeT2Int32( (*siter).find("]") );
        int32_t len = regEnd - regStart - 1;
        std::string regStr = (*siter).substr(regStart + 1, len);

        uint16_t theReg = 0;
        std::istringstream is1( regStr );
        is1 >> theReg;

        int32_t valStart = apgHelper::SizeT2Int32( (*siter).find("x") );
        int32_t valEnd = apgHelper::SizeT2Int32( (*siter).size() );
        len = valEnd - valStart - 1;
        std::string valStr = (*siter).substr(valStart + 1, len);

        uint16_t theValue = 0;
        std::istringstream is2( valStr );
        is2 >> std::hex >> theValue;

        mapOut[theReg] = theValue;
    }

    return mapOut;
}

////////////////////////////
// WRITE        REG
void AltaEthernetIo::WriteReg( uint16_t reg, uint16_t val )
{
    std::string fullUrl = m_url + "/FPGA?WR=" +
                          help::uShort2Str(reg) + "&WD=" + help::uShort2Str(val, true);

    CLibCurlWrap theCurl;

    std::string result;
    theCurl.HttpGet( fullUrl, result );

}

////////////////////////////
// WRITE        MRMD
void AltaEthernetIo::WriteMRMD(const uint16_t reg, const std::vector<uint16_t> &data )
{
    std::vector<uint16_t>::const_iterator iter;
    uint16_t offset = 0;
    for( iter = data.begin(); iter != data.end(); ++iter, ++offset )
    {
        WriteReg( reg + offset, *iter );
    }
}

////////////////////////////
// GET  IMAGE   DATA
void AltaEthernetIo::GetImageData(std::vector<uint16_t> &ImageData)
{
    const int32_t NumBytesExpected =
        apgHelper::SizeT2Int32( ImageData.size() ) * sizeof(uint16_t);

    //grab the data
    std::string fullUrl = m_url + "/UE/image.bin";

    CLibCurlWrap theCurl;
    std::string result;
    theCurl.HttpGet( fullUrl, result );

    if( NumBytesExpected !=  apgHelper::SizeT2Int32( result.size() ) )
    {
        std::stringstream received;
        received <<  result.size();

        std::stringstream requested;
        requested << NumBytesExpected;

        std::string errMsg = fullUrl + " error - " + requested.str() \
                             + " bytes requested " + received.str() + " bytes received.";
        apgHelper::throwRuntimeException( m_fileName, errMsg,
                                          __LINE__, Apg::ErrorType_Critical );
    }

    std::string::iterator strIter;
    std::string::iterator strIterNext;
    int32_t i = 0;

    for(strIter = result.begin(); strIter != result.end(); strIter += 2, ++i)
    {
        strIterNext = strIter + 1;
        uint8_t a = (*strIter);
        uint8_t b = (*strIterNext);

        //TODO verify this works on mac...
        uint16_t v = ((a << 8) | b);
        ImageData.at(i) = v;
    }
}

////////////////////////////
// SETUP     IMG     XFER
void AltaEthernetIo::SetupImgXfer(const uint16_t Rows,
                                  const uint16_t Cols, const uint16_t NumOfImages,
                                  const bool IsBulkSeq)
{

    if( !IsBulkSeq)
    {
        apgHelper::throwRuntimeException( m_fileName,
                                          "Bulk sequence must be active for AltaEthernetIo",
                                          __LINE__, Apg::ErrorType_InvalidUsage );
    }

    //in alta code the number of frames is rolled into the image height
    uint32_t rolledPixels = NumOfImages * Rows;

    std::stringstream rolled;
    rolled << rolledPixels;

    const std::string fullUrl = m_url + "/FPGA?CI=0,0," + help::uShort2Str(Cols)
                                + "," + rolled.str() + ",0xFFFFFFFF";

    CLibCurlWrap theCurl;

    std::string result;
    theCurl.HttpGet( fullUrl, result );

}

////////////////////////////
//      CANCEL     IMG         XFER
void AltaEthernetIo::CancelImgXfer()
{
    std::string errStr("CancelImgXfer not supported on alta ethernet cameras.");
    apgHelper::throwRuntimeException( m_fileName, errStr,
                                      __LINE__, Apg::ErrorType_InvalidOperation );
}

////////////////////////////
// GET      STATUS
void AltaEthernetIo::GetStatus(CameraStatusRegs::BasicStatus &status)
{

    std::map<uint16_t, uint16_t> statusMap =
        ReadRegs( m_StatusRegs );

    //TODO - verify that this is OK
    //setting the values we don't have to zero
    status.CoolerDrive =  FindValInMap(
                              CameraRegs::TEMP_DRIVE, statusMap);

    status.FetchCount = 0;

    status.InputVoltage = FindValInMap(
                              CameraRegs::INPUT_VOLTAGE, statusMap);

    status.SequenceCounter = FindValInMap(
                                 CameraRegs::SEQUENCE_COUNTER, statusMap);

    status.Status =  FindValInMap(
                         CameraRegs::STATUS, statusMap);

    status.TdiCounter = FindValInMap(
                            CameraRegs::TDI_COUNTER, statusMap);

    status.TempCcd = FindValInMap(
                         CameraRegs::TEMP_CCD, statusMap);

    status.TempHeatSink = FindValInMap(
                              CameraRegs::TEMP_HEATSINK, statusMap);

    status.uFrame = 0;

    status.DataAvailFlag = (status.Status & CameraRegs::STATUS_IMAGE_DONE_BIT);
}

////////////////////////////
// GET      STATUS
void AltaEthernetIo::GetStatus(CameraStatusRegs::AdvStatus &status)
{
    CameraStatusRegs::BasicStatus basic;
    memset(&basic, 0, sizeof(basic));
    GetStatus( basic );

    //TODO - verify that this is OK
    //setting the values we don't have to zero
    status.CoolerDrive = basic.CoolerDrive;
    status.CurrentFrame = 0;
    status.DataAvailFlag = basic.DataAvailFlag;
    status.FetchCount = basic.FetchCount;
    status.InputVoltage = basic.InputVoltage;
    status.MostRecentFrame = 0;
    status.ReadyFrame = 0;
    status.SequenceCounter = basic.SequenceCounter;
    status.Status = basic.Status;
    status.TdiCounter = basic.TdiCounter;
    status.TempCcd = basic.TempCcd;
    status.TempHeatSink = basic.TempHeatSink;
    status.uFrame = basic.uFrame;
}

////////////////////////////
//  GET      MAC      ADDRESS
void AltaEthernetIo::GetMacAddress( std::string &Mac )
{

    const std::string fullUrl = m_url + "/NVRAM?Tag=10&Length=6&Get";

    CLibCurlWrap theCurl;

    std::string result;
    theCurl.HttpGet( fullUrl, result );

    const std::string dataUrl = m_url + "/UE/nvram.bin";
    theCurl.HttpGet( dataUrl, Mac );

}

////////////////////////////
//  REBOOT
void AltaEthernetIo::Reboot()
{
    const std::string fullUrl = m_url + "/REBOOT?Submit=Reboot";

    CLibCurlWrap theCurl;

    std::string result;
    theCurl.HttpGet( fullUrl, result );

}

////////////////////////////
// WRITE        SRMD
void AltaEthernetIo::WriteSRMD(const uint16_t reg, const std::vector<uint16_t> &data )
{
    std::string base = m_url + "/FPGA?WR=" + help::uShort2Str(reg);
    std::string fullUrl = base;
    std::vector<uint16_t>::const_iterator iter;

    int32_t count = 0;

    for( iter = data.begin(); iter != data.end(); ++iter )
    {
        std::string temp = "&WD=" + mkHexRegStr( *iter );
        fullUrl.append( temp );

        if( MAX_WRITES_PER_URL - 1 == count )
        {
            //send the max data
            CLibCurlWrap theCurl;
            std::string result;
            theCurl.HttpGet( fullUrl, result );

            //reset
            count = 0;
            fullUrl = base;
        }
        else
        {
            ++count;
        }
    }

    //send any remaining data
    if( count )
    {
        CLibCurlWrap theCurl;
        std::string result;
        theCurl.HttpGet( fullUrl, result );
    }
}

////////////////////////////
//  GET    NETWORK     SETTINGS
std::string AltaEthernetIo::GetNetworkSettings()
{
    std::string mac;
    GetMacAddress( mac );

    std::string result = "Mac Address: " + mac + "\n";

    return result;
}

////////////////////////////
//      GET    DRIVER   VERSION
std::string AltaEthernetIo::GetDriverVersion()
{
    CLibCurlWrap theCurl;
    return theCurl.GetVerison();
}

////////////////////////////
// GET       FIRMWARE    REV
uint16_t AltaEthernetIo::GetFirmwareRev()
{
    return ReadReg( CameraRegs::FIRMWARE_REV );
}


////////////////////////////
//  GET  INFO
std::string AltaEthernetIo::GetInfo()
{
    std::stringstream result;

    result << "Interface: Ethernet\n";
    result << "Camera Firmware: " << GetFirmwareRev() << "\n";
    result << GetNetworkSettings().c_str() << "\n";
    return result.str();
}

////////////////////////////
//      SET     SERIAL       BAUD     RATE
void AltaEthernetIo::SetSerialBaudRate( const uint16_t PortId, const uint32_t BaudRate )
{
    std::string fullUrl = m_url + "/SERCFG?SetBitRate=" +
                          GetPortStr( PortId ) + "," + uint32ToStr( BaudRate );

    CLibCurlWrap theCurl;

    std::string result;
    theCurl.HttpGet( fullUrl, result );
}

////////////////////////////
//      GET     SERIAL       BAUD     RATE
uint32_t AltaEthernetIo::GetSerialBaudRate(  const uint16_t PortId  )
{
    const std::string finalUrl = m_url + "/SERCFG?GetBitRate=" + GetPortStr( PortId );

    CLibCurlWrap theCurl;
    std::string result;
    theCurl.HttpGet( finalUrl, result );

    std::vector<std::string> tokens = help::MakeTokens(result, ",");

    uint32_t BaudRate = 0;
    std::istringstream is( tokens[2] );
    is >>  BaudRate;

    return BaudRate;

}

////////////////////////////
//   GET    SERIAL   FLOW     CONTROL
Apg::SerialFC AltaEthernetIo::GetSerialFlowControl( uint16_t PortId )
{
    const std::string finalUrl = m_url + "/SERCFG?GetFlowControl=" + GetPortStr( PortId );

    CLibCurlWrap theCurl;
    std::string result;
    theCurl.HttpGet( finalUrl, result );

    std::vector<std::string> tokens = help::MakeTokens(result, ",");

    Apg::SerialFC flow = Apg::SerialFC_Unknown;

    if( 0 == tokens[2].compare( "N" ) )
    {
        flow = Apg::SerialFC_Off;
    }

    if( 0 == tokens[2].compare( "S" ) )
    {
        flow = Apg::SerialFC_On;
    }

    return flow;
}

////////////////////////////
//   SET    SERIAL   FLOW     CONTROL
void AltaEthernetIo::SetSerialFlowControl( uint16_t PortId,
        const Apg::SerialFC FlowControl )
{
    std::string cflowStr;
    switch( FlowControl )
    {
        case Apg::SerialFC_Off:
            cflowStr.append("N");
            break;

        case Apg::SerialFC_On:
            cflowStr.append("S");
            break;

        default:
        {
            std::stringstream msg;
            msg <<  "Invalid SerialFlowControl value = " << FlowControl;
            apgHelper::throwRuntimeException( m_fileName, msg.str(),
                                              __LINE__, Apg::ErrorType_InvalidUsage );
        }
        break;
    }

    const std::string fullUrl = m_url + "/SERCFG?SetFlowControl=" + GetPortStr( PortId ) +
                                "," + cflowStr;

    CLibCurlWrap theCurl;

    std::string result;
    theCurl.HttpGet( fullUrl, result );

}

////////////////////////////
//  GET    SERIAL    PARITY
Apg::SerialParity AltaEthernetIo::GetSerialParity( uint16_t PortId )
{
    const std::string finalUrl = m_url + "/SERCFG?GetParityBits=" + GetPortStr( PortId );

    CLibCurlWrap theCurl;
    std::string result;
    theCurl.HttpGet( finalUrl, result );

    std::vector<std::string> tokens = help::MakeTokens(result, ",");

    Apg::SerialParity parity = Apg::SerialParity_Unknown;

    if( 0 == tokens[2].compare( "N" ) )
    {
        parity = Apg::SerialParity_None;
    }

    if( 0 == tokens[2].compare( "O" ) )
    {
        parity = Apg::SerialParity_Odd;
    }

    if( 0 == tokens[2].compare( "E" ) )
    {
        parity = Apg::SerialParity_Even;
    }

    return parity;
}

////////////////////////////
//  SET    SERIAL    PARITY
void AltaEthernetIo::SetSerialParity( uint16_t PortId,
                                      const Apg::SerialParity Parity )
{
    std::string parityStr;
    switch( Parity  )
    {
        case Apg::SerialParity_Even:
            parityStr.append("E");
            break;

        case Apg::SerialParity_Odd:
            parityStr.append("O");
            break;

        case Apg::SerialParity_None:
            parityStr.append("N");
            break;

        default:
        {
            std::stringstream msg;
            msg <<  "Invalid Parity value = " << Parity;
            apgHelper::throwRuntimeException( m_fileName, msg.str(),
                                              __LINE__, Apg::ErrorType_InvalidUsage );
        }
        break;
    }

    const std::string fullUrl = m_url + "/SERCFG?SetParityBits=" + GetPortStr( PortId ) +
                                "," + parityStr;

    CLibCurlWrap theCurl;

    std::string result;
    theCurl.HttpGet( fullUrl, result );

}

////////////////////////////
//      READ       SERIAL
void AltaEthernetIo::ReadSerial( uint16_t, std::string & )
{
    apgHelper::throwRuntimeException( m_fileName, "Not implemented",
                                      __LINE__, Apg::ErrorType_InvalidUsage );
}

////////////////////////////
//      WRITE       SERIAL
void AltaEthernetIo::WriteSerial( uint16_t, const std::string & )
{
    apgHelper::throwRuntimeException( m_fileName, "Not implemented",
                                      __LINE__, Apg::ErrorType_InvalidUsage );
}

