/*
 * Copyright (c) 2019-2020, NVIDIA CORPORATION.  All rights reserved.
 *
 * NVIDIA CORPORATION and its licensors retain all intellectual property
 * and proprietary rights in and to this software, related documentation
 * and any modifications thereto.  Any use, reproduction, disclosure or
 * distribution of this software and related documentation without an express
 * license agreement from NVIDIA CORPORATION is strictly prohibited.
 */

#include "MAX96712cameramodule/CNvMMAX96712CameraModule.hpp"
#include "devices/MAX96712DeserializerDriver/CNvMMax96712.hpp"
#include "ModuleIF/CNvMCameraModuleExport.hpp"

extern "C" {
    #include "cdi_debug_log.h"
}

namespace nvsipl
{

SIPLStatus
CNvMMAX96712CameraModule::Init()
{
    SIPLStatus status;

    std::unique_ptr<CNvMSerializer> broadcastSerializer = std::move(CreateBroadcastSerializer());

    m_broadcastSensor = std::move(CreateBroadcastSensor());

    status = m_pDeserializer->EnableLinks(1 << m_linkIndex);
    CHK_NVSIPLEERROR_AND_RETURN(status, "Enable all transport links");

    if (m_groupInitProg) {
        // Use the broadcast serializer to initialize all transport links
        status = m_upTransport->Init(broadcastSerializer->GetCDIDeviceHandle(), m_initLinkMask, m_groupInitProg);
        CHK_NVSIPLEERROR_AND_RETURN(status, "TransportLink Init");

        // Detect the sensor
        status = DetectModule();
        CHK_NVSIPLEERROR_AND_RETURN(status, "Sensor Detection");

        /* check if m_linkIndex is for the last link */
        if (((m_initLinkMask & (0xF << m_linkIndex)) & 0xF) == (1 << m_linkIndex)) {
            // Do the post sensor init
            status = m_pDeserializer->EnableLinks(m_initLinkMask);
            CHK_NVSIPLEERROR_AND_RETURN(status, "Enable All Transport Links");

            status = m_pDeserializer->CheckLinkLock(m_initLinkMask);
            CHK_NVSIPLEERROR_AND_RETURN(status, "Check Link Lock");

            // Initialize the sensors
            status = InitModule();
            CHK_NVSIPLEERROR_AND_RETURN(status, "Sensor Init");

            status = m_upTransport->PostSensorInit(broadcastSerializer->GetCDIDeviceHandle(), m_initLinkMask, m_groupInitProg);
            CHK_NVSIPLEERROR_AND_RETURN(status, "TransportLink PostInit");
        }
    } else {
        // Use the broadcast serializer to initialize all transport links
        status = m_upTransport->Init(broadcastSerializer->GetCDIDeviceHandle(), m_initLinkMask, m_groupInitProg);
        CHK_NVSIPLEERROR_AND_RETURN(status, "TransportLink Init");

        // Detect the sensor
        status = DetectModule();
        CHK_NVSIPLEERROR_AND_RETURN(status, "Sensor Detection");

        // Initialize the sensors
        status = InitModule();
        CHK_NVSIPLEERROR_AND_RETURN(status, "Sensor Init");

        status = m_upTransport->PostSensorInit(broadcastSerializer->GetCDIDeviceHandle(), m_initLinkMask, m_groupInitProg);
        CHK_NVSIPLEERROR_AND_RETURN(status, "TransportLink PostInit");
    }

    status = m_broadcastSensor->Deinit();
    CHK_NVSIPLEERROR_AND_RETURN(status, "Broadcast Sensor Deinit");

    return status;
}

SIPLStatus
CNvMMAX96712CameraModule::PostInit()
{
    SIPLStatus status;

    status = m_upTransport->MiscInit();
    CHK_NVSIPLEERROR_AND_RETURN(status, "TransportLink misc initialization");

    m_groupInitProg = false;

    return NVSIPL_STATUS_OK;
}

SIPLStatus
CNvMMAX96712CameraModule::Start()
{
    SIPLStatus status;

    // Start the individual transport links
    status = m_upTransport->Start();
    CHK_NVSIPLEERROR_AND_RETURN(status, "TransportLink Start");

    status = StartModule();
    CHK_NVSIPLEERROR_AND_RETURN(status, "Sensor Start");

    return NVSIPL_STATUS_OK;
}

SIPLStatus
CNvMMAX96712CameraModule::Stop()
{
    SIPLStatus status;

    // Stop the sensors
    status = StopModule();
    CHK_NVSIPLEERROR_AND_RETURN(status, "Sensor Stop");

    // Stop the transport links
    status = m_upTransport->Stop();
    CHK_NVSIPLEERROR_AND_RETURN(status, "TransportLink Stop");

    return NVSIPL_STATUS_OK;
}

SIPLStatus
CNvMMAX96712CameraModule::Reconfigure()
{
    SIPLStatus status, statusLinkControl;
    std::unique_ptr<CNvMSerializer> broadcastSerializer = std::move(CreateBroadcastSerializer());
    std::vector<CNvMDeserializer::LinkAction> linkAction;
    CNvMDeserializer::LinkAction item;
    NvMediaStatus nvmStatus = NVMEDIA_STATUS_OK;

    status = m_upSensor->Reset();
    if (status != NVSIPL_STATUS_OK) {
        LOG_ERR("Sensor Reset failed in CNvMCameraModule::Reconfigure\n");
        return status;
    }

    if (m_upEeprom) {
        status = m_upEeprom->Reset();
        if (status != NVSIPL_STATUS_OK) {
            LOG_ERR("EEPROM Reset failed in CNvMCameraModule::Reconfigure\n");
            return status;
        }
    }

    status = m_upSerializer->Reset();
    CHK_NVSIPLEERROR_AND_RETURN(status, "Serializer Reset");

    status = m_upTransport->Reset();
    CHK_NVSIPLEERROR_AND_RETURN(status, "TransportLink Reset");

    // Enable the link for the camera module
    item.linkIdx = m_linkIndex;
    item.action = CNvMDeserializer::LinkAction::LINK_ENABLE;
    linkAction.push_back(item);
    status = m_pDeserializer->ControlLinks(linkAction);
    CHK_NVSIPLEERROR_AND_RETURN(status, "ControlLinks in CNvMMAX96712CameraModule::Reconfigure");

    // Oneshot reset
    nvmStatus = MAX96712OneShotReset(m_pDeserializer->GetCDIDeviceHandle(), (LinkMAX96712)(1 << m_linkIndex));
    CHK_NVMERROR_CONVERT_AND_RETURN(nvmStatus, "MAX96712OneShotReset");

    // Add additional delays to get module stable as link rebuild can hit over 100ms
    SLEEP_MS(100);

    // Use the broadcast serializer to initialize all transport links
    status = m_upTransport->Init(broadcastSerializer->GetCDIDeviceHandle(), (1 << m_linkIndex), m_groupInitProg);
    if (status != NVSIPL_STATUS_OK) {
        // Disable the link for the camera module
        item.linkIdx = m_linkIndex;
        item.action = CNvMDeserializer::LinkAction::LINK_DISABLE;
        linkAction.clear();
        linkAction.push_back(item);
        statusLinkControl = m_pDeserializer->ControlLinks(linkAction);
        CHK_NVSIPLEERROR_AND_RETURN(statusLinkControl, "ControlLinks in CNvMMAX96712CameraModule::Reconfigure");
    }
    CHK_NVSIPLEERROR_AND_RETURN(status, "TransportLink Init");

    // Detect the sensor
    status = DetectModule();
    if (status != NVSIPL_STATUS_OK) {
        // Disable the link for the camera module
        item.linkIdx = m_linkIndex;
        item.action = CNvMDeserializer::LinkAction::LINK_DISABLE;
        linkAction.clear();
        linkAction.push_back(item);
        statusLinkControl = m_pDeserializer->ControlLinks(linkAction);
        CHK_NVSIPLEERROR_AND_RETURN(statusLinkControl, "ControlLinks in CNvMMAX96712CameraModule::Reconfigure");
    }
    CHK_NVSIPLEERROR_AND_RETURN(status, "Sensor Detection");

    // Initialize the sensors
    status = InitModule();
    if (status != NVSIPL_STATUS_OK) {
        // Disable the link for the camera module
        item.linkIdx = m_linkIndex;
        item.action = CNvMDeserializer::LinkAction::LINK_DISABLE;
        linkAction.clear();
        linkAction.push_back(item);
        statusLinkControl = m_pDeserializer->ControlLinks(linkAction);
        CHK_NVSIPLEERROR_AND_RETURN(statusLinkControl, "ControlLinks in CNvMMAX96712CameraModule::Reconfigure");
    }
    CHK_NVSIPLEERROR_AND_RETURN(status, "Sensor Init");

    status = m_upTransport->PostSensorInit(broadcastSerializer->GetCDIDeviceHandle(), 1 << m_linkIndex, m_groupInitProg);
    if (status != NVSIPL_STATUS_OK) {
        // Disable the link for the camera module
        item.linkIdx = m_linkIndex;
        item.action = CNvMDeserializer::LinkAction::LINK_DISABLE;
        linkAction.clear();
        linkAction.push_back(item);
        statusLinkControl = m_pDeserializer->ControlLinks(linkAction);
        CHK_NVSIPLEERROR_AND_RETURN(statusLinkControl, "ControlLinks in CNvMMAX96712CameraModule::Reconfigure");
    }
    CHK_NVSIPLEERROR_AND_RETURN(status, "TransportLink PostInit");

    status = m_upTransport->MiscInit();
    if (status != NVSIPL_STATUS_OK) {
        // Disable the link for the camera module
        item.linkIdx = m_linkIndex;
        item.action = CNvMDeserializer::LinkAction::LINK_DISABLE;
        linkAction.clear();
        linkAction.push_back(item);
        statusLinkControl = m_pDeserializer->ControlLinks(linkAction);
        CHK_NVSIPLEERROR_AND_RETURN(statusLinkControl, "ControlLinks in CNvMMAX96712CameraModule::Reconfigure");
    }
    CHK_NVSIPLEERROR_AND_RETURN(status, "TransportLink misc initialization");

    return NVSIPL_STATUS_OK;
}

SIPLStatus
CNvMMAX96712CameraModule::ReadEEPROMData(const std::uint16_t address,
                                         const std::uint32_t length,
                                         std::uint8_t * const buffer)
{
    return m_upEeprom->ReadData(address, length, buffer);
}

#if !NV_IS_SAFETY
SIPLStatus
CNvMMAX96712CameraModule::WriteEEPROMData(const std::uint16_t address,
                                          const std::uint32_t length,
                                          std::uint8_t * const buffer)
{
    return m_upEeprom->WriteData(address, length, buffer);
}
#endif // !NV_IS_SAFETY

SIPLStatus
CNvMMAX96712CameraModule::ToggleLED(bool enable)
{
    return NVSIPL_STATUS_NOT_SUPPORTED;
}

SIPLStatus
CNvMMAX96712CameraModule::Deinit()
{
    SIPLStatus status;

    // Deinit the sensors
    status = DeinitModule();
    CHK_NVSIPLEERROR_AND_RETURN(status, "Sensor Deinit");

    // Deinit hte transport links
    status = m_upTransport->Deinit();
    CHK_NVSIPLEERROR_AND_RETURN(status, "TransportLink Deinit");

    return NVSIPL_STATUS_OK;
}

SIPLStatus
CNvMMAX96712CameraModule::SetConfig(
    const CameraModuleConfig *cameraModuleConfig,
    const uint8_t linkIndex)
{
    SIPLStatus status;

    // Save device params and serializer info
    m_oDeviceParams = *cameraModuleConfig->params;
    m_oSerInfo = cameraModuleConfig->cameraModuleInfo->serInfo;
    m_oSensorInfo = cameraModuleConfig->cameraModuleInfo->sensorInfo;
    m_pDeserializer = cameraModuleConfig->deserializer;
    m_sModuleName = cameraModuleConfig->cameraModuleInfo->name;

    const CameraModuleInfo *moduleInfo = cameraModuleConfig->cameraModuleInfo;

    // Config serializer
    m_upSerializer = std::move(CreateNewSerializer());

    m_oDeviceParams.bUseNativeI2C = false;
    status = m_upSerializer->SetConfig(&moduleInfo->serInfo, &m_oDeviceParams);
    CHK_NVSIPLEERROR_AND_RETURN(status, "Serializer SetConfig");

    // Config Sensor
    m_upSensor.reset(new CNvMSensor());
    status = m_upSensor->SetConfig(&moduleInfo->sensorInfo, cameraModuleConfig->params);
    SetConfigModule(&moduleInfo->sensorInfo, cameraModuleConfig->params);
    m_upSensor->SetDriverHandle(GetCDIDeviceDriver());
    m_upSensor->SetDriverContext(GetCDIDeviceContext());
    CHK_NVSIPLEERROR_AND_RETURN(status, "Sensor SetConfig");

    // Config EEPROM
    if (moduleInfo->isEEPROMSupported) {
        m_upEeprom.reset(new CNvMEEPROM());
        status = m_upEeprom->SetConfig(&moduleInfo->eepromInfo, cameraModuleConfig->params);
        m_upEeprom->SetDriverHandle(GetCDIDeviceDriver(MODULE_COMPONENT_EEPROM));
        CHK_NVSIPLEERROR_AND_RETURN(status, "EEPROM SetConfig");
    }

    // Set Link Index
    m_linkIndex = moduleInfo->linkIndex;
    m_initLinkMask = cameraModuleConfig->initLinkMask;
    m_groupInitProg = cameraModuleConfig->groupInitProg;

    SetupConnectionProperty(cameraModuleConfig, linkIndex);

    return NVSIPL_STATUS_OK;
}

CNvMCameraModule::Property*
CNvMMAX96712CameraModule::GetCameraModuleProperty()
{
    return m_upCameraModuleProperty.get();
}

void CNvMMAX96712CameraModule::SetSensorConnectionProperty(ConnectionProperty::SensorConnectionProperty *sensorConnectionProperty) {
    // Default properties
    sensorConnectionProperty->sensorReset.isNeeded = false;
    sensorConnectionProperty->sensorReset.pinNum = 0;
    sensorConnectionProperty->sensorReset.releaseResetLevel = false;

    sensorConnectionProperty->frameSync.pinNum = 8;

    sensorConnectionProperty->refClock.isNeeded = false;
    sensorConnectionProperty->refClock.pinNum = 0;

    sensorConnectionProperty->phyLanes.isLaneSwapNeeded = true;
    sensorConnectionProperty->phyLanes.isTwoLane = false;

    sensorConnectionProperty->bPostSensorInitFsync = false;

    sensorConnectionProperty->pclk = 0;

    sensorConnectionProperty->vsyncHigh  = 0;
    sensorConnectionProperty->vsyncLow   = 0;
    sensorConnectionProperty->vsyncDelay = 0;
    sensorConnectionProperty->vsyncTrig  = 0;
}

void
CNvMMAX96712CameraModule::SetupConnectionProperty(
    const CameraModuleConfig *cameraModuleConfig,
    const uint8_t linkIndex)
{

    const CameraModuleInfo *cameraModuleInfo = cameraModuleConfig->cameraModuleInfo;

    // Create camera module property and connection property
    m_upCameraModuleProperty = std::move(std::unique_ptr<Property>(new Property));
    m_upCameraModuleConnectionProperty =  std::move(std::unique_ptr<ConnectionProperty>(new ConnectionProperty));

    CNvMSensor* sensor = m_upSensor.get();
    CNvMCameraModule::Property::SensorProperty oSensorProperty = {
        .id = cameraModuleInfo->sensorInfo.id,
        .virtualChannelID = linkIndex,
        .inputFormat = sensor->GetInputFormat(),
        .surfaceType = sensor->GetSurfaceType(),
        .width = sensor->GetWidth(),
        .height = sensor->GetHeight(),
        .startX = 0,
        .startY = 0,
        .embeddedTop = sensor->GetEmbLinesTop(),
        .embeddedBot = sensor->GetEmbLinesBot(),
        .frameRate = sensor->GetFrameRate(),
        .embeddedDataType = sensor->GetEmbDataType(),
        .pCDIHandle = nullptr,
    };

    m_upCameraModuleProperty->sensorProperty = oSensorProperty;

    CNvMCameraModule::ConnectionProperty::SensorConnectionProperty sensorConnectionProperty = {};

    SetSensorConnectionProperty(&sensorConnectionProperty);

    sensorConnectionProperty.uBrdcstSensorAddrs  = sensor->GetNativeI2CAddr();

    sensorConnectionProperty.uVCID = oSensorProperty.virtualChannelID;
    sensorConnectionProperty.inputFormat =  sensor->GetInputFormat();
    sensorConnectionProperty.bEmbeddedDataType =  sensor->GetEmbDataType();
    sensorConnectionProperty.bEnableTriggerModeSync =  sensor->GetEnableExtSync();
    sensorConnectionProperty.fFrameRate =  sensor->GetFrameRate();
    sensorConnectionProperty.height = sensor->GetHeight();
    sensorConnectionProperty.width = sensor->GetWidth();
    sensorConnectionProperty.embeddedTop = sensor->GetEmbLinesTop();
    sensorConnectionProperty.embeddedBot = sensor->GetEmbLinesBot();
    sensorConnectionProperty.bEnableTPG = sensor->GetEnableTPG();
    sensorConnectionProperty.sensorDescription = sensor->GetSensorDescription();

    m_upCameraModuleConnectionProperty->sensorConnectionProperty = sensorConnectionProperty;

    CNvMCameraModule::Property::EEPROMProperty oEepromProperty = {
        .isEEPROMSupported = cameraModuleInfo->isEEPROMSupported,
        .pCDIHandle = nullptr,
    };

    m_upCameraModuleProperty->eepromProperty = oEepromProperty;
}

std::unique_ptr<CNvMSerializer>
CNvMMAX96712CameraModule::CreateBroadcastSerializer()
{
    CNvMDevice::DeviceParams params = m_oDeviceParams;
    params.bUseNativeI2C = true;
    std::unique_ptr<CNvMSerializer> up = std::move(CreateNewSerializer());
    up->SetConfig(&m_oSerInfo, &params);
    up->CreateCDIDevice(m_pCDIRoot);
    return up;
}

std::unique_ptr<CNvMSensor>
CNvMMAX96712CameraModule::CreateBroadcastSensor()
{
    CNvMDevice::DeviceParams params = m_oDeviceParams;
    params.bUseNativeI2C = true;
    std::unique_ptr<CNvMSensor> up(new CNvMSensor());
    up->SetConfig(&m_oSensorInfo, &params);
    up->SetDriverHandle(GetCDIDeviceDriver());
    up->SetDriverContext(GetCDIDeviceContext());
    up->CreateCDIDevice(m_pCDIRoot);
    return up;
}

SIPLStatus
CNvMMAX96712CameraModule::CreateCDIDevice(
    DevBlkCDIRootDevice* cdiRoot)
{
    SIPLStatus status;

    m_pCDIRoot = cdiRoot;

    // Create serializer
    status = m_upSerializer->CreateCDIDevice(cdiRoot);
    CHK_NVSIPLEERROR_AND_RETURN(status, "Serializer CreateCDIDevice");

    // Create Sensor
    status = m_upSensor->CreateCDIDevice(cdiRoot);
    CHK_NVSIPLEERROR_AND_RETURN(status, "Sensor CreateCDIDevice");

    m_upCameraModuleProperty->sensorProperty.pCDIHandle = m_upSensor->GetCDIDeviceHandle();
    m_upCameraModuleConnectionProperty->sensorConnectionProperty.uSensorAddrs  = m_upSensor->GetI2CAddr();

    // Create EEPROM
    if (m_upCameraModuleProperty->eepromProperty.isEEPROMSupported) {
        status = m_upEeprom->CreateCDIDevice(cdiRoot);
        CHK_NVSIPLEERROR_AND_RETURN(status, "EEPROM CreateCDIDevice");

        m_upCameraModuleProperty->eepromProperty.pCDIHandle = m_upEeprom->GetCDIDeviceHandle();
        m_upCameraModuleConnectionProperty->eepromAddr = m_upEeprom->GetI2CAddr();
        m_upCameraModuleConnectionProperty->brdcstEepromAddr = m_upEeprom->GetNativeI2CAddr();
    } else {
        m_upCameraModuleConnectionProperty->eepromAddr = UINT8_MAX;
    }

    // Config transport link
    m_upTransport = std::move(CreateNewTransportLink());
    CNvMTransportLink::LinkParams params{};
    params.pSerCDIDevice = m_upSerializer->GetCDIDeviceHandle();
    params.pDeserCDIDevice = m_pDeserializer->GetCDIDeviceHandle();
    params.ulinkIndex = m_linkIndex;
    params.uBrdcstSerAddr = m_upSerializer->GetNativeI2CAddr();
    params.uSerAddr = m_upSerializer->GetI2CAddr();
    params.moduleConnectionProperty = *m_upCameraModuleConnectionProperty;
    params.bEnableSimulator = m_oDeviceParams.bEnableSimulator;
    params.bSlave = m_oDeviceParams.bSlave;
    params.m_groupInitProg = m_groupInitProg;

    status = m_upTransport->SetConfig(&params);
    CHK_NVSIPLEERROR_AND_RETURN(status, "TransportLink SetConfig");

    return NVSIPL_STATUS_OK;
}

uint16_t
CNvMMAX96712CameraModule::GetPowerOnDelayMs()
{
    if (CNvMDeserializer::LinkMode::LINK_MODE_GMSL2 == GetLinkMode()) {
        return 100u; /* GMSL2 Link lock time maximum is 100ms*/
    } else {
        return 20u; /* GMSL1 Link lock time 20ms, I2C wake time is typical 1.1ms after releasing PWDN */
    }
}

std::string
CNvMMAX96712CameraModule::GetSupportedDeserailizer()
{
    return "MAX96712";
}

DevBlkCDIDeviceDriver*
CNvMMAX96712CameraModule::GetCDIDeviceDriver(
    ModuleComponent component)
{
    return GetCDIDeviceDriver();
}

const CNvMCameraModule::Version*
CNvMCameraModule_GetVersion()
{
    static const CNvMCameraModule::Version version;
    return &version;
}

void
CNvMCameraModule_SetDebugLevel(
    CNvSIPLDeviceBlockTrace::TraceLevel level)
{
    // Set the trace level used by the camera module files
    CNvSIPLDeviceBlockTrace::GetTrace().SetLevel(level);
}

} // end of namespace
