/*
 * Copyright (C) 2023 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 "satellite_control.h"

#include "cellular_call_hisysevent.h"
#include "cellular_call_register.h"
#include "module_service_utils.h"
#include "securec.h"
#include "standardize_utils.h"

namespace OHOS {
namespace Telephony {

SatelliteControl::~SatelliteControl()
{
    ReleaseAllConnection();
}

int32_t SatelliteControl::Dial(const CellularCallInfo &callInfo, bool isEcc)
{
    TELEPHONY_LOGI("DialSatellite start");
    DelayedSingleton<CellularCallHiSysEvent>::GetInstance()->SetCallParameterInfo(
        callInfo.slotId, static_cast<int32_t>(callInfo.callType), callInfo.videoState);
    int32_t ret = DialPreJudgment(callInfo, false);
    if (ret != TELEPHONY_SUCCESS) {
        return ret;
    }
    StandardizeUtils standardizeUtils;
    // Remove the phone number separator
    std::string newPhoneNum = standardizeUtils.RemoveSeparatorsPhoneNumber(callInfo.phoneNum);

    CLIRMode clirMode = CLIRMode::DEFAULT;
    if (IsNeedExecuteMMI(callInfo.slotId, newPhoneNum, clirMode, false)) {
        TELEPHONY_LOGI("DialSatellite return, mmi code type.");
        return RETURN_TYPE_MMI;
    }

    std::lock_guard<std::recursive_mutex> lock(connectionMapMutex_);
    if (!CanCall(connectionMap_)) {
        TELEPHONY_LOGE("DialSatellite return, error type: call state error.");
        CellularCallHiSysEvent::WriteDialCallFaultEvent(callInfo.slotId, static_cast<int32_t>(callInfo.callType),
            callInfo.videoState, CALL_ERR_CALL_COUNTS_EXCEED_LIMIT, "DSatellite dial call state error");
        return CALL_ERR_CALL_COUNTS_EXCEED_LIMIT;
    }

    return EncapsulateDialCommon(callInfo.slotId, newPhoneNum, clirMode);
}

int32_t SatelliteControl::EncapsulateDialCommon(int32_t slotId, const std::string &phoneNum, CLIRMode &clirMode)
{
    pendingPhoneNumber_ = phoneNum;
    DialRequestStruct dialRequest;
    /**
     * <idx>: integer type;
     * call identification number as described in 3GPP TS 22.030 [19] subclause 4.5.5.1
     * this number can be used in +CHLD command operations
     * <dir>:
     */
    dialRequest.phoneNum = phoneNum;

    /**
     * <n> (parameter sets the adjustment for outgoing calls):
     *  0	presentation indicator is used according to the subscription of the CLIR service
     *  1	CLIR invocation
     *  2	CLIR suppression
     */
    dialRequest.clirMode = clirMode;

    /**
     * An example of voice group call service request usage:
     * ATD*17*753#500; (originate voice group call with the priority level 3)
     * OK (voice group call setup was successful)
     */
    CellularCallConnectionSatellite satelliteConnection;
    return satelliteConnection.DialRequest(slotId, dialRequest);
}

int32_t SatelliteControl::HangUp(const CellularCallInfo &callInfo, CallSupplementType type)
{
    TELEPHONY_LOGI("HangUp start");
    switch (type) {
        case CallSupplementType::TYPE_DEFAULT: {
            // Match the session connection according to the phone number string
            std::lock_guard<std::recursive_mutex> lock(connectionMapMutex_);
            auto pConnection = FindConnectionByIndex<SatelliteConnectionMap &, CellularCallConnectionSatellite *>(
                connectionMap_, callInfo.index);
            if (pConnection == nullptr) {
                TELEPHONY_LOGE("HangUp, error type: connection is null");
                CellularCallHiSysEvent::WriteHangUpFaultEvent(
                    callInfo.slotId, callInfo.callId, CALL_ERR_CALL_CONNECTION_NOT_EXIST, "HangUp pConnection is null");
                return CALL_ERR_CALL_CONNECTION_NOT_EXIST;
            }

            if (DelayedSingleton<CellularCallRegister>::GetInstance() != nullptr) {
                DelayedSingleton<CellularCallRegister>::GetInstance()->ReportSingleCallInfo(
                    pConnection->GetCallReportInfo(), TelCallState::CALL_STATUS_DISCONNECTING);
            }
            return pConnection->HangUpRequest(callInfo.slotId);
        }
        default: {
            TELEPHONY_LOGE("HangUp warring, type is invalid");
            CellularCallHiSysEvent::WriteHangUpFaultEvent(
                callInfo.slotId, callInfo.callId, TELEPHONY_ERR_ARGUMENT_INVALID, "HangUp type is invalid");
            return TELEPHONY_ERR_ARGUMENT_INVALID;
        }
    }
}

int32_t SatelliteControl::Answer(const CellularCallInfo &callInfo)
{
    TELEPHONY_LOGI("SatelliteControl::Answer start");
    std::lock_guard<std::recursive_mutex> lock(connectionMapMutex_);
    auto pConnection = FindConnectionByIndex<SatelliteConnectionMap &, CellularCallConnectionSatellite *>(
        connectionMap_, callInfo.index);
    if (pConnection == nullptr) {
        TELEPHONY_LOGE("Answer return, error type: connection is null");
        CellularCallHiSysEvent::WriteAnswerCallFaultEvent(callInfo.slotId, callInfo.callId, callInfo.videoState,
            CALL_ERR_CALL_CONNECTION_NOT_EXIST, "get connection data is null");
        return CALL_ERR_CALL_CONNECTION_NOT_EXIST;
    }
    // There is an active call when you call, or third party call waiting
    if (IsInState(connectionMap_, TelCallState::CALL_STATUS_ACTIVE) ||
        pConnection->GetStatus() == TelCallState::CALL_STATUS_WAITING) {
        TELEPHONY_LOGI("Answer there is an active call when you call, or third party call waiting");
        auto con = FindConnectionByState<SatelliteConnectionMap &, CellularCallConnectionSatellite *>(
            connectionMap_, TelCallState::CALL_STATUS_ACTIVE);
        if (con != nullptr) {
            TELEPHONY_LOGI("Answer: There is an active session currently, and it needs to hold");
            con->HangUpRequest(callInfo.slotId);
        } else {
            TELEPHONY_LOGE("Answer return, error type: con is null, there are no active calls");
        }
    }

    if (pConnection->GetStatus() == TelCallState::CALL_STATUS_INCOMING ||
        pConnection->GetStatus() == TelCallState::CALL_STATUS_ALERTING ||
        pConnection->GetStatus() == TelCallState::CALL_STATUS_WAITING) {
        return pConnection->AnswerRequest(callInfo.slotId);
    }

    TELEPHONY_LOGE("SatelliteControl::Answer return, error type: call state error, phone not ringing.");
    CellularCallHiSysEvent::WriteAnswerCallFaultEvent(callInfo.slotId, callInfo.callId, callInfo.videoState,
        CALL_ERR_CALL_STATE, "call state error phone not ringing");
    return CALL_ERR_CALL_STATE;
}

int32_t SatelliteControl::Reject(const CellularCallInfo &callInfo)
{
    TELEPHONY_LOGI("SatelliteControl::Reject start");
    std::lock_guard<std::recursive_mutex> lock(connectionMapMutex_);
    auto pConnection = FindConnectionByIndex<SatelliteConnectionMap &, CellularCallConnectionSatellite *>(
        connectionMap_, callInfo.index);
    if (pConnection == nullptr) {
        TELEPHONY_LOGE("SatelliteControl::Reject, error type: connection is null");
        CellularCallHiSysEvent::WriteHangUpFaultEvent(
            callInfo.slotId, callInfo.callId, CALL_ERR_CALL_CONNECTION_NOT_EXIST, "Reject pConnection is null");
        return CALL_ERR_CALL_CONNECTION_NOT_EXIST;
    }
    if (!pConnection->IsRingingState()) {
        TELEPHONY_LOGE("CSControl::Reject return, error type: call state error, phone not ringing.");
        CellularCallHiSysEvent::WriteHangUpFaultEvent(
            callInfo.slotId, callInfo.callId, CALL_ERR_CALL_STATE, "Reject call state error phone not ringing");
        return CALL_ERR_CALL_STATE;
    }
    if (DelayedSingleton<CellularCallRegister>::GetInstance() != nullptr) {
        DelayedSingleton<CellularCallRegister>::GetInstance()->ReportSingleCallInfo(
            pConnection->GetCallReportInfo(), TelCallState::CALL_STATUS_DISCONNECTING);
    }
    return pConnection->RejectRequest(callInfo.slotId);
}

int32_t SatelliteControl::ReportSatelliteCallsData(int32_t slotId, const SatelliteCurrentCallList &callInfoList)
{
    std::lock_guard<std::recursive_mutex> lock(connectionMapMutex_);
    if (callInfoList.callSize <= 0) {
        return ReportHangUpInfo(slotId);
    } else if (callInfoList.callSize > 0 && connectionMap_.empty()) {
        return ReportIncomingInfo(slotId, callInfoList);
    } else if (callInfoList.callSize > 0 && !connectionMap_.empty()) {
        return ReportUpdateInfo(slotId, callInfoList);
    }
    return TELEPHONY_ERROR;
}

int32_t SatelliteControl::ReportUpdateInfo(int32_t slotId, const SatelliteCurrentCallList &callInfoList)
{
    TELEPHONY_LOGD("ReportUpdateInfo entry");
    CallsReportInfo callsReportInfo;
    for (int32_t i = 0; i < callInfoList.callSize; ++i) {
        CallReportInfo reportInfo = EncapsulationCallReportInfo(slotId, callInfoList.calls[i]);

        auto pConnection = FindConnectionByIndex<SatelliteConnectionMap &, CellularCallConnectionSatellite *>(
            connectionMap_, callInfoList.calls[i].index);
        if (pConnection == nullptr) {
            CellularCallConnectionSatellite connection;
            connection.SetOrUpdateCallReportInfo(reportInfo);
            connection.SetFlag(true);
            connection.SetIndex(callInfoList.calls[i].index);
            connection.SetNumber(callInfoList.calls[i].number);
            SetConnectionData(connectionMap_, callInfoList.calls[i].index, connection);
        } else {
            pConnection->SetFlag(true);
            pConnection->SetIndex(callInfoList.calls[i].index);
            pConnection->SetOrUpdateCallReportInfo(reportInfo);
            pConnection->SetNumber(callInfoList.calls[i].number);
        }
        callsReportInfo.callVec.push_back(reportInfo);
    }
    callsReportInfo.slotId = slotId;
    DeleteConnection(callsReportInfo, callInfoList);
    if (DelayedSingleton<CellularCallRegister>::GetInstance() == nullptr) {
        TELEPHONY_LOGE("ReportUpdateInfo return, GetInstance() is nullptr.");
        return TELEPHONY_ERR_LOCAL_PTR_NULL;
    }
    if (!isIgnoredIncomingCall_) {
        DelayedSingleton<CellularCallRegister>::GetInstance()->ReportCallsInfo(callsReportInfo);
    }
    return TELEPHONY_SUCCESS;
}

void SatelliteControl::DeleteConnection(CallsReportInfo &callsReportInfo, const SatelliteCurrentCallList &callInfoList)
{
    TELEPHONY_LOGI("DeleteConnection entry");
    auto it = connectionMap_.begin();
    while (it != connectionMap_.end()) {
        CallReportInfo callReportInfo = it->second.GetCallReportInfo();
        if (!it->second.GetFlag()) {
            callReportInfo.state = TelCallState::CALL_STATUS_DISCONNECTED;
            callsReportInfo.callVec.push_back(callReportInfo);
            connectionMap_.erase(it++);
            GetCallFailReason(callsReportInfo.slotId, connectionMap_);
        } else {
            it->second.SetFlag(false);
            ++it;
        }
    }
}

CallReportInfo SatelliteControl::EncapsulationCallReportInfo(int32_t slotId, const SatelliteCurrentCall &callInfo)
{
    CallReportInfo callReportInfo;
    if (memset_s(&callReportInfo, sizeof(callReportInfo), 0, sizeof(callReportInfo)) != EOK) {
        TELEPHONY_LOGE("EncapsulationCallReportInfo return, memset_s fail.");
        return callReportInfo;
    }
    size_t cpyLen = strlen(callInfo.number.c_str()) + 1;
    if (cpyLen > static_cast<size_t>(kMaxNumberLen + 1)) {
        TELEPHONY_LOGE("EncapsulationCallReportInfo return, strcpy_s fail.");
        return callReportInfo;
    }
    if (strcpy_s(callReportInfo.accountNum, cpyLen, callInfo.number.c_str()) != EOK) {
        TELEPHONY_LOGE("EncapsulationCallReportInfo return, strcpy_s fail.");
        return callReportInfo;
    }
    callReportInfo.index = callInfo.index;
    callReportInfo.accountId = slotId;
    callReportInfo.voiceDomain = callInfo.voiceDomain;
    callReportInfo.state = static_cast<TelCallState>(callInfo.state);
    callReportInfo.callType = CallType::TYPE_SATELLITE;
    callReportInfo.callMode = VideoStateType::TYPE_VOICE;
    callReportInfo.mpty = callInfo.mpty;
    return callReportInfo;
}

int32_t SatelliteControl::ReportIncomingInfo(int32_t slotId, const SatelliteCurrentCallList &callInfoList)
{
    TELEPHONY_LOGI("ReportIncomingInfo entry");
    CallsReportInfo callsReportInfo;
    for (int32_t i = 0; i < callInfoList.callSize; ++i) {
        CallReportInfo cellularCallReportInfo = EncapsulationCallReportInfo(slotId, callInfoList.calls[i]);

        CellularCallConnectionSatellite connection;
        connection.SetStatus(static_cast<TelCallState>(callInfoList.calls[i].state));
        connection.SetIndex(callInfoList.calls[i].index);
        connection.SetOrUpdateCallReportInfo(cellularCallReportInfo);
        connection.SetNumber(callInfoList.calls[i].number);
        SetConnectionData(connectionMap_, callInfoList.calls[i].index, connection);

        callsReportInfo.callVec.push_back(cellularCallReportInfo);
    }
    if (DelayedSingleton<CellularCallRegister>::GetInstance() == nullptr) {
        TELEPHONY_LOGE("ReportIncomingInfo return, GetInstance() is nullptr.");
        return TELEPHONY_ERR_ARGUMENT_INVALID;
    }
    callsReportInfo.slotId = slotId;
    if (!DelayedSingleton<CellularCallRegister>::GetInstance()->IsCallManagerCallBackRegistered() &&
        callsReportInfo.callVec.size() != 0 && callsReportInfo.callVec[0].state == TelCallState::CALL_STATUS_INCOMING) {
        isIgnoredIncomingCall_ = true;
    } else {
        DelayedSingleton<CellularCallRegister>::GetInstance()->ReportCallsInfo(callsReportInfo);
    }
    return TELEPHONY_SUCCESS;
}

int32_t SatelliteControl::ReportHangUpInfo(int32_t slotId)
{
    TELEPHONY_LOGD("ReportHangUpInfo entry");
    CallsReportInfo callsReportInfo;
    for (auto &it : connectionMap_) {
        CallReportInfo callReportInfo = it.second.GetCallReportInfo();
        callReportInfo.state = TelCallState::CALL_STATUS_DISCONNECTED;
        callReportInfo.accountId = slotId;
        callsReportInfo.callVec.push_back(callReportInfo);
        GetCallFailReason(slotId, connectionMap_);
    }
    if (connectionMap_.empty()) {
        TELEPHONY_LOGI("connectionMap_ is empty");
        CallReportInfo reportInfo;
        reportInfo.state = TelCallState::CALL_STATUS_DISCONNECTED;
        reportInfo.accountId = slotId;
        callsReportInfo.callVec.push_back(reportInfo);
    }
    if (DelayedSingleton<CellularCallRegister>::GetInstance() == nullptr) {
        TELEPHONY_LOGE("ReportHangUpInfo return, GetInstance() is nullptr.");
        return TELEPHONY_ERR_LOCAL_PTR_NULL;
    }
    callsReportInfo.slotId = slotId;
    if (isIgnoredIncomingCall_) {
        isIgnoredIncomingCall_ = false;
    } else {
        DelayedSingleton<CellularCallRegister>::GetInstance()->ReportCallsInfo(callsReportInfo);
    }
    ReleaseAllConnection();
    return TELEPHONY_SUCCESS;
}

void SatelliteControl::ReleaseAllConnection()
{
    TELEPHONY_LOGI("ReleaseAllConnection entry");
    std::lock_guard<std::recursive_mutex> lock(connectionMapMutex_);
    connectionMap_.clear();
}

SatelliteConnectionMap SatelliteControl::GetConnectionMap()
{
    TELEPHONY_LOGI("GetConnectionMap entry");
    std::lock_guard<std::recursive_mutex> lock(connectionMapMutex_);
    return connectionMap_;
}

int32_t SatelliteControl::ReportHangUp(const std::vector<CellularCallInfo> &infos, int32_t slotId)
{
    CallsReportInfo callsReportInfo;
    callsReportInfo.slotId = slotId;
    for (const auto &info : infos) {
        if (info.callType == CallType::TYPE_SATELLITE && info.slotId == slotId) {
            CallReportInfo satelliteCallReportInfo;
            if (memset_s(satelliteCallReportInfo.accountNum, kMaxNumberLen + 1, 0, kMaxNumberLen + 1) != EOK) {
                TELEPHONY_LOGE("memset_s fail");
                return TELEPHONY_ERR_MEMSET_FAIL;
            }
            if (memcpy_s(satelliteCallReportInfo.accountNum, kMaxNumberLen, info.phoneNum, kMaxNumberLen) != EOK) {
                TELEPHONY_LOGE("memcpy_s fail");
                return TELEPHONY_ERR_MEMCPY_FAIL;
            }
            satelliteCallReportInfo.index = info.index;
            satelliteCallReportInfo.accountId = info.slotId;
            satelliteCallReportInfo.callType = CallType::TYPE_SATELLITE;
            satelliteCallReportInfo.callMode = VideoStateType::TYPE_VOICE;
            satelliteCallReportInfo.state = TelCallState::CALL_STATUS_DISCONNECTED;
            callsReportInfo.callVec.push_back(satelliteCallReportInfo);
        }
    }
    if (DelayedSingleton<CellularCallRegister>::GetInstance() == nullptr) {
        TELEPHONY_LOGE("CellularCallRegister instance is nullptr");
        return TELEPHONY_ERR_LOCAL_PTR_NULL;
    }
    DelayedSingleton<CellularCallRegister>::GetInstance()->ReportCallsInfo(callsReportInfo);
    ReleaseAllConnection();
    return TELEPHONY_SUCCESS;
}

int32_t SatelliteControl::HoldCall(int32_t slotId)
{
    return TELEPHONY_ERROR;
}

int32_t SatelliteControl::UnHoldCall(int32_t slotId)
{
    return TELEPHONY_ERROR;
}

int32_t SatelliteControl::SwitchCall(int32_t slotId)
{
    return TELEPHONY_ERROR;
}

int32_t SatelliteControl::CombineConference(int32_t slotId)
{
    return TELEPHONY_ERROR;
}

int32_t SatelliteControl::HangUpAllConnection(int32_t slotId)
{
    TELEPHONY_LOGI("HangUpAllConnection entry");
    CellularCallConnectionSatellite connection;
    // The AT command for hanging up all calls is the same as the AT command for rejecting calls,
    // so the reject interface is reused.
    connection.RejectRequest(slotId);
    return TELEPHONY_SUCCESS;
}

int32_t SatelliteControl::ReportCallsData(int32_t slotId, const CallInfoList &callInfoList)
{
    return TELEPHONY_ERROR;
}

int32_t SatelliteControl::ExecutePostDial(int32_t slotId, int64_t callId)
{
    TELEPHONY_LOGI("ExecutePostDial entry");
    std::lock_guard<std::recursive_mutex> lock(connectionMapMutex_);
    if (connectionMap_.empty()) {
        TELEPHONY_LOGE("connectionMap_ is empty.");
        return TELEPHONY_ERROR;
    }
    auto pConnection = FindConnectionByIndex<SatelliteConnectionMap &,
        CellularCallConnectionSatellite *>(connectionMap_, callId);
    if (pConnection == nullptr) {
        return TELEPHONY_ERR_LOCAL_PTR_NULL;
    }
    char currentChar;
    PostDialCallState state = pConnection->ProcessNextChar(slotId, currentChar);
    switch (state) {
        case PostDialCallState::POST_DIAL_CALL_STARTED:
            DelayedSingleton<CellularCallRegister>::GetInstance()->ReportPostDialChar(currentChar);
            break;
        case PostDialCallState::POST_DIAL_CALL_DELAY:
            DelayedSingleton<CellularCallRegister>::GetInstance()->ReportPostDialDelay(
                pConnection->GetLeftPostDialCallString());
            break;
        default:
            break;
    }
    return TELEPHONY_SUCCESS;
}

int32_t SatelliteControl::PostDialProceed(const CellularCallInfo &callInfo, const bool proceed)
{
    TELEPHONY_LOGI("PostDialProceed entry");
    std::string networkAddress;
    std::string postDialString;
    StandardizeUtils standardizeUtils;
    standardizeUtils.ExtractAddressAndPostDial(callInfo.phoneNum, networkAddress, postDialString);
    std::lock_guard<std::recursive_mutex> lock(connectionMapMutex_);
    auto pConnection = FindConnectionByIndex<SatelliteConnectionMap &, CellularCallConnectionSatellite *>(
        connectionMap_, callInfo.index);
    if (pConnection == nullptr) {
        TELEPHONY_LOGE("cs pConnection is nullptr!");
        return TELEPHONY_ERR_LOCAL_PTR_NULL;
    }
    if (proceed) {
        ExecutePostDial(callInfo.slotId, pConnection->GetIndex());
    } else {
        pConnection->SetPostDialCallState(PostDialCallState::POST_DIAL_CALL_CANCELED);
    }
    return TELEPHONY_SUCCESS;
}
} // namespace Telephony
} // namespace OHOS