/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "core_manager_inner.h"

#include "cellular_data_hisysevent.h"
#include "cellular_data_service.h"
#include "data_service_ext_wrapper.h"
#include "telephony_ext_wrapper.h"

namespace OHOS {
namespace Telephony {
DataConnectionMonitor::DataConnectionMonitor(int32_t slotId) : TelEventHandler("DataConnectionMonitor"), slotId_(slotId)
{
    trafficManager_ = std::make_unique<TrafficManagement>(slotId);
    stallDetectionTrafficManager_ = std::make_unique<TrafficManagement>(slotId);
    if (trafficManager_ == nullptr || stallDetectionTrafficManager_ == nullptr) {
        TELEPHONY_LOGE("TrafficManager or stallDetectionTrafficManager init failed");
    }
}

void DataConnectionMonitor::HandleScreenStateChanged(bool isScreenOn)
{
    if (isScreenOn_ == isScreenOn) {
        return;
    }
    isScreenOn_ = isScreenOn;
    const int32_t defSlotId = CoreManagerInner::GetInstance().GetDefaultCellularDataSlotId();
    if (slotId_ != defSlotId && !IsVsimEnabled()) {
        return;
    }
    StopStallDetectionTimer();
    StartStallDetectionTimer();
}

bool DataConnectionMonitor::IsVsimEnabled()
{
#ifdef OHOS_BUILD_ENABLE_TELEPHONY_EXT
    if (TELEPHONY_EXT_WRAPPER.getVSimSlotId_ && TELEPHONY_EXT_WRAPPER.isVSimEnabled_) {
        int vSimSlotId = INVALID_SLOT_ID;
        TELEPHONY_EXT_WRAPPER.getVSimSlotId_(vSimSlotId);
        if (vSimSlotId == slotId_ && TELEPHONY_EXT_WRAPPER.isVSimEnabled_()) {
            return true;
        }
    }
#endif
    return false;
}

bool DataConnectionMonitor::IsAggressiveRecovery()
{
    return (dataRecoveryState_ == RecoveryState::STATE_CLEANUP_CONNECTIONS) ||
        (dataRecoveryState_ == RecoveryState::STATE_REREGISTER_NETWORK) ||
        (dataRecoveryState_ == RecoveryState::STATE_RADIO_STATUS_RESTART);
}
 
int32_t DataConnectionMonitor::GetStallDetectionPeriod()
{
    if (isScreenOn_ || IsAggressiveRecovery()) {
        return DATA_STALL_ALARM_AGGRESSIVE_DELAY_IN_MS_DEFAULT;
    }
    return DATA_STALL_ALARM_NON_AGGRESSIVE_DELAY_IN_MS_DEFAULT;
}

void DataConnectionMonitor::StartStallDetectionTimer()
{
    TELEPHONY_LOGD("Slot%{public}d: start stall detection", slotId_);
    stallDetectionEnabled_ = true;
    int32_t stallDetectionPeriod = GetStallDetectionPeriod();
    TELEPHONY_LOGD("stallDetectionPeriod = %{public}d", stallDetectionPeriod);
    if (!HasInnerEvent(CellularDataEventCode::MSG_STALL_DETECTION_EVENT_ID)) {
        AppExecFwk::InnerEvent::Pointer event =
            AppExecFwk::InnerEvent::Get(CellularDataEventCode::MSG_STALL_DETECTION_EVENT_ID);
        SendEvent(event, stallDetectionPeriod, Priority::LOW);
    }
}

__attribute__((no_sanitize("cfi"))) void DataConnectionMonitor::OnStallDetectionTimer()
{
    TELEPHONY_LOGD("Slot%{public}d: on stall detection", slotId_);
#ifdef OHOS_BUILD_ENABLE_DATA_SERVICE_EXT
    if (DATA_SERVICE_EXT_WRAPPER.requestTcpAndDnsPackets_) {
        DATA_SERVICE_EXT_WRAPPER.requestTcpAndDnsPackets_();
        return;
    }
#endif
    UpdateFlowInfo();
    int32_t dataState = static_cast<int32_t>(DataConnectState::DATA_STATE_UNKNOWN);
    DelayedRefSingleton<CellularDataService>::GetInstance().GetCellularDataState(dataState);
    if (noRecvPackets_ > RECOVERY_TRIGGER_PACKET ||
        (dataState != static_cast<int32_t>(DataConnectionStatus::DATA_STATE_CONNECTED) &&
        dataRecoveryState_ == RecoveryState::STATE_RADIO_STATUS_RESTART)) {
        HandleRecovery();
        noRecvPackets_ = 0;
    }
    int32_t stallDetectionPeriod = GetStallDetectionPeriod();
    TELEPHONY_LOGD("stallDetectionPeriod = %{public}d", stallDetectionPeriod);
    if (!HasInnerEvent(CellularDataEventCode::MSG_STALL_DETECTION_EVENT_ID) && stallDetectionEnabled_) {
        AppExecFwk::InnerEvent::Pointer event =
            AppExecFwk::InnerEvent::Get(CellularDataEventCode::MSG_STALL_DETECTION_EVENT_ID);
        SendEvent(event, stallDetectionPeriod, Priority::LOW);
    }
}

void DataConnectionMonitor::StopStallDetectionTimer()
{
    TELEPHONY_LOGD("Slot%{public}d: stop stall detection", slotId_);
    stallDetectionEnabled_ = false;
    RemoveEvent(CellularDataEventCode::MSG_STALL_DETECTION_EVENT_ID);
}

void DataConnectionMonitor::UpdateFlowInfo()
{
    if (stallDetectionTrafficManager_ == nullptr) {
        TELEPHONY_LOGE("Slot%{public}d: stallDetectionTrafficManager_ is null", slotId_);
        return;
    }
    int64_t previousSentPackets = 0;
    int64_t previousRecvPackets = 0;
    int64_t currentSentPackets = 0;
    int64_t currentRecvPackets = 0;
    stallDetectionTrafficManager_->GetPacketData(previousSentPackets, previousRecvPackets);
    stallDetectionTrafficManager_->UpdatePacketData();
    stallDetectionTrafficManager_->GetPacketData(currentSentPackets, currentRecvPackets);
    int64_t sentPackets = currentSentPackets - previousSentPackets;
    int64_t recvPackets = currentRecvPackets - previousRecvPackets;
    if (sentPackets > 0 && recvPackets == 0) {
        noRecvPackets_ += sentPackets;
    } else if ((sentPackets > 0 && recvPackets > 0) || (sentPackets == 0 && recvPackets > 0)) {
        noRecvPackets_ = 0;
        dataRecoveryState_ = RecoveryState::STATE_REQUEST_CONTEXT_LIST;
    } else {
        TELEPHONY_LOGD("Slot%{public}d: Update Flow Info nothing to do", slotId_);
    }
}

void DataConnectionMonitor::HandleRecovery()
{
    if (callState_ != static_cast<int32_t>(TelCallStatus::CALL_STATUS_IDLE) &&
        callState_ != static_cast<int32_t>(TelCallStatus::CALL_STATUS_DISCONNECTED)) {
        TELEPHONY_LOGI("Slot%{public}d: Stop recovery while call is busy", slotId_);
        dataRecoveryState_ = RecoveryState::STATE_REQUEST_CONTEXT_LIST;
        return;
    }
    switch (dataRecoveryState_) {
        case RecoveryState::STATE_REQUEST_CONTEXT_LIST: {
            TELEPHONY_LOGI("Slot%{public}d: Handle Recovery: get data call list", slotId_);
            dataRecoveryState_ = RecoveryState::STATE_CLEANUP_CONNECTIONS;
            GetPdpContextList();
            CellularDataHiSysEvent::WriteDataDeactiveBehaviorEvent(slotId_, DataDisconnectCause::ON_THE_NETWORK_SIDE);
            break;
        }
        case RecoveryState::STATE_CLEANUP_CONNECTIONS: {
            TELEPHONY_LOGI("Slot%{public}d: Handle Recovery: cleanup connections", slotId_);
            dataRecoveryState_ = RecoveryState::STATE_REREGISTER_NETWORK;
            int32_t ret = DelayedRefSingleton<CellularDataService>::GetInstance().ClearAllConnections(
                slotId_, (int32_t) DisConnectionReason::REASON_RETRY_CONNECTION);
            if (ret != static_cast<int32_t>(RequestNetCode::REQUEST_SUCCESS)) {
                TELEPHONY_LOGE("Slot%{public}d: Handle Recovery: cleanup connections failed", slotId_);
            }
            break;
        }
        case RecoveryState::STATE_REREGISTER_NETWORK: {
            TELEPHONY_LOGI("Slot%{public}d: Handle Recovery: re-register network", slotId_);
            dataRecoveryState_ = RecoveryState::STATE_RADIO_STATUS_RESTART;
            ReregisterNetwork();
            break;
        }
        case RecoveryState::STATE_RADIO_STATUS_RESTART: {
            TELEPHONY_LOGI("Slot%{public}d: Handle Recovery: radio restart", slotId_);
            dataRecoveryState_ = RecoveryState::STATE_REQUEST_CONTEXT_LIST;
            int32_t ret = DelayedRefSingleton<CellularDataService>::GetInstance().ClearAllConnections(
                slotId_, (int32_t)DisConnectionReason::REASON_RETRY_CONNECTION);
            if (ret != static_cast<int32_t>(RequestNetCode::REQUEST_SUCCESS)) {
                TELEPHONY_LOGE("Slot%{public}d: Handle Recovery: radio restart cleanup connections failed", slotId_);
            }
            SetRadioState(CORE_SERVICE_POWER_OFF, RadioEvent::RADIO_OFF);
            break;
        }
        default: {
            TELEPHONY_LOGE("Slot%{public}d: Handle Recovery is falsie", slotId_);
            break;
        }
    }
}

void DataConnectionMonitor::BeginNetStatistics()
{
    updateNetStat_ = true;
    UpdateNetTrafficState();
}

void DataConnectionMonitor::UpdateCallState(int32_t state)
{
    callState_ = state;
}

void DataConnectionMonitor::EndNetStatistics()
{
    RemoveEvent(CellularDataEventCode::MSG_RUN_MONITOR_TASK);
    updateNetStat_ = false;
    if (dataFlowType_ != CellDataFlowType::DATA_FLOW_TYPE_NONE) {
        dataFlowType_ = CellDataFlowType::DATA_FLOW_TYPE_NONE;
        StateNotification::GetInstance().OnUpDataFlowtype(slotId_, dataFlowType_);
    }
}

void DataConnectionMonitor::UpdateNetTrafficState()
{
    if (!HasInnerEvent(CellularDataEventCode::MSG_RUN_MONITOR_TASK) && updateNetStat_) {
        UpdateDataFlowType();
        AppExecFwk::InnerEvent::Pointer event =
            AppExecFwk::InnerEvent::Get(CellularDataEventCode::MSG_RUN_MONITOR_TASK);
        SendEvent(event, DEFAULT_NET_STATISTICS_PERIOD);
    }
}

void DataConnectionMonitor::GetPdpContextList()
{
    CoreManagerInner::GetInstance().GetPdpContextList(slotId_,
        RadioEvent::RADIO_DATA_CALL_LIST_CHANGED, shared_from_this());
}

void DataConnectionMonitor::SetRadioState(const int32_t &radioState, const int32_t &eventCode)
{
    CoreManagerInner::GetInstance().SetRadioState(slotId_, eventCode, radioState, 0, shared_from_this());
}

void DataConnectionMonitor::ReregisterNetwork()
{
    bool isProcess = false;
#ifdef OHOS_BUILD_ENABLE_TELEPHONY_EXT
    if (TELEPHONY_EXT_WRAPPER.reRegisterNetwork_) {
        TELEPHONY_EXT_WRAPPER.reRegisterNetwork_(slotId_, isProcess);
    }
#endif
    TELEPHONY_LOGI("ReregisterNetwork isProcess:%{public}d", isProcess);
    if (isProcess) {
        return;
    }
    CoreManagerInner::GetInstance().GetPreferredNetworkPara(slotId_,
        RadioEvent::RADIO_GET_PREFERRED_NETWORK_MODE, shared_from_this());
}

void DataConnectionMonitor::SetPreferredNetworkPara(const AppExecFwk::InnerEvent::Pointer &event)
{
    std::shared_ptr<PreferredNetworkTypeInfo> preferredNetworkInfo = event->GetSharedObject<PreferredNetworkTypeInfo>();
    if (preferredNetworkInfo == nullptr) {
        TELEPHONY_LOGE("preferredNetworkInfo is null");
        return;
    }
    int32_t networkType = preferredNetworkInfo->preferredNetworkType;
    CoreManagerInner::GetInstance().SetPreferredNetworkPara(slotId_,
        RadioEvent::RADIO_SET_PREFERRED_NETWORK_MODE, networkType, shared_from_this());
}

RecoveryState DataConnectionMonitor::GetDataRecoveryState()
{
    return dataRecoveryState_;
}

void DataConnectionMonitor::UpdateDataFlowType()
{
    if (trafficManager_ == nullptr) {
        TELEPHONY_LOGE("Slot%{public}d: trafficManager is null", slotId_);
        return;
    }
    int64_t previousSentPackets = 0;
    int64_t previousRecvPackets = 0;
    int64_t currentSentPackets = 0;
    int64_t currentRecvPackets = 0;
    trafficManager_->GetPacketData(previousSentPackets, previousRecvPackets);
    trafficManager_->UpdatePacketData();
    trafficManager_->GetPacketData(currentSentPackets, currentRecvPackets);
    int64_t sentPackets = currentSentPackets - previousSentPackets;
    int64_t recvPackets = currentRecvPackets - previousRecvPackets;
    CellDataFlowType previousDataFlowType = dataFlowType_;
    if (previousSentPackets != 0 || previousRecvPackets != 0) {
        if (sentPackets > 0 && recvPackets == 0) {
            dataFlowType_ = CellDataFlowType::DATA_FLOW_TYPE_UP;
        } else if (sentPackets == 0 && recvPackets > 0) {
            dataFlowType_ = CellDataFlowType::DATA_FLOW_TYPE_DOWN;
        } else if (sentPackets > 0 && recvPackets > 0) {
            dataFlowType_ = CellDataFlowType::DATA_FLOW_TYPE_UP_DOWN;
        } else {
            dataFlowType_ = CellDataFlowType::DATA_FLOW_TYPE_NONE;
        }
    }
    if (previousDataFlowType != dataFlowType_) {
        StateNotification::GetInstance().OnUpDataFlowtype(slotId_, dataFlowType_);
    }
}

CellDataFlowType DataConnectionMonitor::GetDataFlowType()
{
    return dataFlowType_;
}

void DataConnectionMonitor::SetDataFlowType(CellDataFlowType dataFlowType)
{
    if (dataFlowType_ != dataFlowType) {
        dataFlowType_ = dataFlowType;
        StateNotification::GetInstance().OnUpDataFlowtype(slotId_, dataFlowType_);
    }
}

void DataConnectionMonitor::IsNeedDoRecovery(bool needDoRecovery)
{
    if (needDoRecovery) {
        HandleRecovery();
    } else {
        dataRecoveryState_ = RecoveryState::STATE_REQUEST_CONTEXT_LIST;
    }
    int32_t stallDetectionPeriod = GetStallDetectionPeriod();
    TELEPHONY_LOGD("stallDetectionPeriod = %{public}d", stallDetectionPeriod);
    if (!HasInnerEvent(CellularDataEventCode::MSG_STALL_DETECTION_EVENT_ID) && stallDetectionEnabled_) {
        AppExecFwk::InnerEvent::Pointer event =
            AppExecFwk::InnerEvent::Get(CellularDataEventCode::MSG_STALL_DETECTION_EVENT_ID);
        SendEvent(event, stallDetectionPeriod, Priority::LOW);
    }
}

void DataConnectionMonitor::ProcessEvent(const AppExecFwk::InnerEvent::Pointer &event)
{
    if (event == nullptr) {
        TELEPHONY_LOGE("Slot%{public}d: event is null", slotId_);
        return;
    }
    uint32_t eventID = event->GetInnerEventId();
    switch (eventID) {
        case CellularDataEventCode::MSG_RUN_MONITOR_TASK: {
            UpdateNetTrafficState();
            break;
        }
        case CellularDataEventCode::MSG_STALL_DETECTION_EVENT_ID:
            OnStallDetectionTimer();
            break;
        case RadioEvent::RADIO_DATA_CALL_LIST_CHANGED:
            TELEPHONY_LOGI("Slot%{public}d: radio call list changed complete", slotId_);
            break;
        case RadioEvent::RADIO_GET_PREFERRED_NETWORK_MODE:
            SetPreferredNetworkPara(event);
            break;
        case RadioEvent::RADIO_SET_PREFERRED_NETWORK_MODE:
            TELEPHONY_LOGI("Slot%{public}d: set preferred network mode complete", slotId_);
            break;
        case RadioEvent::RADIO_OFF:
            SetRadioState(CORE_SERVICE_POWER_ON, RadioEvent::RADIO_ON);
            break;
        case RadioEvent::RADIO_ON:
            TELEPHONY_LOGI("Slot%{public}d: set radio state on complete", slotId_);
            break;
        default:
            TELEPHONY_LOGI("Slot%{public}d: connection monitor ProcessEvent code = %{public}u", slotId_, eventID);
            break;
    }
}
} // namespace Telephony
} // namespace OHOS