/*
 * Copyright 2017 The Android Open Source Project
 *
 * 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 "mcap_test_app.h"
#include "mca_defs.h"

namespace SYSTEM_BT_TOOLS_MCAP_TOOL {

#define CASE_RETURN_STR(const) \
  case const:                  \
    return #const;

static const char* dump_mcap_events(const uint8_t event) {
  switch (event) {
    CASE_RETURN_STR(MCA_ERROR_RSP_EVT)
    CASE_RETURN_STR(MCA_CREATE_IND_EVT)
    CASE_RETURN_STR(MCA_CREATE_CFM_EVT)
    CASE_RETURN_STR(MCA_RECONNECT_IND_EVT)
    CASE_RETURN_STR(MCA_RECONNECT_CFM_EVT)
    CASE_RETURN_STR(MCA_ABORT_IND_EVT)
    CASE_RETURN_STR(MCA_ABORT_CFM_EVT)
    CASE_RETURN_STR(MCA_DELETE_IND_EVT)
    CASE_RETURN_STR(MCA_DELETE_CFM_EVT)
    CASE_RETURN_STR(MCA_SYNC_CAP_IND_EVT)
    CASE_RETURN_STR(MCA_SYNC_CAP_CFM_EVT)
    CASE_RETURN_STR(MCA_SYNC_SET_IND_EVT)
    CASE_RETURN_STR(MCA_SYNC_SET_CFM_EVT)
    CASE_RETURN_STR(MCA_SYNC_INFO_IND_EVT)
    CASE_RETURN_STR(MCA_CONNECT_IND_EVT)
    CASE_RETURN_STR(MCA_DISCONNECT_IND_EVT)
    CASE_RETURN_STR(MCA_OPEN_IND_EVT)
    CASE_RETURN_STR(MCA_OPEN_CFM_EVT)
    CASE_RETURN_STR(MCA_CLOSE_IND_EVT)
    CASE_RETURN_STR(MCA_CLOSE_CFM_EVT)
    CASE_RETURN_STR(MCA_CONG_CHG_EVT)
    CASE_RETURN_STR(MCA_RSP_TOUT_IND_EVT)
    default:
      return "Unknown event";
  }
}

static void print_mcap_event(const tMCA_DISCONNECT_IND* mcap_disconnect_ind) {
  printf("%s: peer_bd_addr=%s,l2cap_disconnect_reason=0x%04x\n", __func__,
         mcap_disconnect_ind->bd_addr.ToString().c_str(),
         mcap_disconnect_ind->reason);
}

static void print_mcap_event(const tMCA_CONNECT_IND* mcap_connect_ind) {
  printf("%s: peer_bd_addr=%s, peer_mtu=%d \n", __func__,
         mcap_connect_ind->bd_addr.ToString().c_str(), mcap_connect_ind->mtu);
}

static void print_mcap_event(const tMCA_RSP_EVT* mcap_rsp) {
  printf("%s: response, mdl_id=%d, op_code=0x%02x, rsp_code=0x%02x\n", __func__,
         mcap_rsp->mdl_id, mcap_rsp->op_code, mcap_rsp->rsp_code);
}

static void print_mcap_event(const tMCA_EVT_HDR* mcap_evt_hdr) {
  printf("%s: event, mdl_id=%d, op_code=0x%02x\n", __func__,
         mcap_evt_hdr->mdl_id, mcap_evt_hdr->op_code);
}

static void print_mcap_event(const tMCA_CREATE_IND* mcap_create_ind) {
  printf("%s: mdl_id=%d, op_code=0x%02x, dep_id=%d, cfg=0x%02x\n", __func__,
         mcap_create_ind->mdl_id, mcap_create_ind->op_code,
         mcap_create_ind->dep_id, mcap_create_ind->cfg);
}

static void print_mcap_event(const tMCA_CREATE_CFM* mcap_create_cfm) {
  printf("%s: mdl_id=%d, op_code=0x%02x, rsp_code=%d, cfg=0x%02x\n", __func__,
         mcap_create_cfm->mdl_id, mcap_create_cfm->op_code,
         mcap_create_cfm->rsp_code, mcap_create_cfm->cfg);
}

static void print_mcap_event(const tMCA_DL_OPEN* mcap_dl_open) {
  printf("%s: mdl_id=%d, mdl_handle=%d, mtu=%d\n", __func__,
         mcap_dl_open->mdl_id, mcap_dl_open->mdl, mcap_dl_open->mtu);
}

static void print_mcap_event(const tMCA_DL_CLOSE* mcap_dl_close) {
  printf("%s: mdl_id=%d, mdl_handle=%d, l2cap_disconnect_reason=0x%04x\n",
         __func__, mcap_dl_close->mdl_id, mcap_dl_close->mdl,
         mcap_dl_close->reason);
}

static void print_mcap_event(const tMCA_CONG_CHG* mcap_congestion_change) {
  printf("%s: mdl_id=%d, mdl_handle=%d, congested=%d\n", __func__,
         mcap_congestion_change->mdl_id, mcap_congestion_change->mdl,
         mcap_congestion_change->cong);
}

McapTestApp::McapTestApp(btmcap_test_interface_t* mcap_test_interface)
    : _mcl_list(), _mdep_list() {
  _mcap_test_interface = mcap_test_interface;
}

btmcap_test_interface_t* McapTestApp::GetInterface() {
  return _mcap_test_interface;
}

bool McapTestApp::Register(uint16_t ctrl_psm, uint16_t data_psm,
                           uint16_t sec_mask, tMCA_CTRL_CBACK* callback) {
  if (!callback) {
    LOG(ERROR) << "callback is null";
    return false;
  }
  _mca_reg.rsp_tout = 5000;
  _mca_reg.ctrl_psm = ctrl_psm;
  _mca_reg.data_psm = data_psm;
  _mca_reg.sec_mask = sec_mask;
  _mcap_handle =
      _mcap_test_interface->register_application(&_mca_reg, callback);
  return _mcap_handle > 0;
}

void McapTestApp::Deregister() {
  _mcap_test_interface->deregister_application(_mcap_handle);
  _mcap_handle = 0;
}

bool McapTestApp::Registered() { return _mcap_handle > 0; }

bool McapTestApp::ConnectMcl(const RawAddress& bd_addr, uint16_t ctrl_psm,
                             uint16_t sec_mask) {
  if (!Registered()) {
    LOG(ERROR) << "Application not registered";
    return false;
  }
  McapMcl* mcap_mcl = FindMclByPeerAddress(bd_addr);
  if (!mcap_mcl) {
    LOG(INFO) << "MCL does not exist, creating new MCL";
    _mcl_list.push_back(McapMcl(_mcap_test_interface, _mcap_handle, bd_addr));
    mcap_mcl = &_mcl_list[_mcl_list.size() - 1];
  }
  if (mcap_mcl->GetHandle() != 0) {
    LOG(ERROR) << "MCL is still active, cannot make another connection";
    return false;
  }
  return mcap_mcl->Connect(ctrl_psm, sec_mask);
}

bool McapTestApp::CreateMdep(uint8_t type, uint8_t max_mdl,
                             tMCA_DATA_CBACK* data_callback) {
  if (!data_callback) {
    LOG(ERROR) << "Data callback is null";
    return false;
  }
  _mdep_list.push_back(McapMdep(_mcap_test_interface, _mcap_handle, type,
                                max_mdl, data_callback));
  return _mdep_list[_mdep_list.size() - 1].Create();
}

uint8_t McapTestApp::GetHandle() { return _mcap_handle; }

McapMcl* McapTestApp::FindMclByPeerAddress(const RawAddress& bd_addr) {
  for (McapMcl& mcl : _mcl_list) {
    if (mcl.GetPeerAddress() == bd_addr) {
      return &mcl;
    }
  }
  return nullptr;
}

McapMcl* McapTestApp::FindMclByHandle(tMCA_CL mcl_handle) {
  for (McapMcl& mcl : _mcl_list) {
    if (mcl.GetHandle() == mcl_handle) {
      return &mcl;
    }
  }
  return nullptr;
}

McapMdep* McapTestApp::FindMdepByHandle(tMCA_DEP mdep_handle) {
  for (McapMdep& mdep : _mdep_list) {
    if (mdep.GetHandle() == mdep_handle) {
      return &mdep;
    }
  }
  return nullptr;
}

void McapTestApp::RemoveMclByHandle(tMCA_CL mcl_handle) {
  LOG(INFO) << "Removing MCL handle " << (int)mcl_handle;
  for (std::vector<McapMcl>::iterator it = _mcl_list.begin();
       it != _mcl_list.end(); ++it) {
    if (it->GetHandle() == mcl_handle) {
      _mcl_list.erase(it);
      LOG(INFO) << "Removed MCL handle " << (int)mcl_handle;
      return;
    }
  }
}

bool McapTestApp::IsRegistered() { return _mcap_handle > 0; }

void McapTestApp::ControlCallback(tMCA_HANDLE handle, tMCA_CL mcl,
                                  uint8_t event, tMCA_CTRL* p_data) {
  McapMcl* mcap_mcl = FindMclByHandle(mcl);
  McapMdl* mcap_mdl = nullptr;
  printf("%s: mcap_handle=%d, mcl_handle=%d, event=%s (0x%02x)\n", __func__,
         handle, mcl, dump_mcap_events(event), event);
  if (_mcap_handle != handle) {
    LOG(ERROR) << "MCAP handle mismatch, self=" << _mcap_handle
               << ", other=" << handle;
    return;
  }
  switch (event) {
    case MCA_ERROR_RSP_EVT:
      print_mcap_event(&p_data->rsp);
      break;

    case MCA_CREATE_CFM_EVT:
      // Called when MCA_CreateMdl succeeded step 1 when response is received
      print_mcap_event(&p_data->create_cfm);
      if (!mcap_mcl) {
        LOG(ERROR) << "No MCL for mcl_handle " << (int)mcl;
        break;
      }
      if (!mcap_mcl->IsConnected()) {
        LOG(ERROR) << "MCL " << (int)mcl << " not connected";
        break;
      }
      mcap_mdl = mcap_mcl->FindMdlById(p_data->create_cfm.mdl_id);
      if (!mcap_mdl) {
        LOG(ERROR) << "MDL not found for id " << p_data->create_cfm.mdl_id;
        break;
      }
      if (mcap_mdl->GetResponseCode() >= 0) {
        LOG(ERROR) << "MDL already got response " << mcap_mdl->GetResponseCode()
                   << " for id " << p_data->create_cfm.mdl_id;
        break;
      }
      mcap_mdl->SetResponseCode(p_data->create_cfm.rsp_code);
      break;

    case MCA_CREATE_IND_EVT: {
      // Should be replied with MCA_CreateMdlRsp
      print_mcap_event(&p_data->create_ind);
      if (!mcap_mcl) {
        LOG(ERROR) << "No MCL for mcl_handle " << (int)mcl;
        break;
      }
      if (!mcap_mcl->IsConnected()) {
        LOG(ERROR) << "MCL " << (int)mcl << " not connected";
        break;
      }
      McapMdep* mcap_mdep = FindMdepByHandle(p_data->create_ind.dep_id);
      if (!mcap_mdep) {
        LOG(ERROR) << "MDEP ID " << (int)p_data->create_ind.dep_id
                   << " does not exist";
        _mcap_test_interface->create_mdl_response(
            mcl, p_data->create_ind.dep_id, p_data->create_ind.mdl_id, 0,
            MCA_RSP_BAD_MDEP, get_test_channel_config());
        break;
      }
      bool ret = mcap_mcl->CreateMdlResponse(
          mcap_mdep->GetHandle(), p_data->create_ind.mdl_id,
          p_data->create_ind.dep_id, p_data->create_ind.cfg);
      LOG(INFO) << (ret ? "SUCCESS" : "FAIL");
      if (!ret) {
        _mcap_test_interface->create_mdl_response(
            mcl, p_data->create_ind.dep_id, p_data->create_ind.mdl_id, 0,
            MCA_RSP_NO_RESOURCE, get_test_channel_config());
      }
      break;
    }
    case MCA_RECONNECT_IND_EVT: {
      // Called when remote device asks to reconnect
      // reply with MCA_ReconnectMdlRsp
      print_mcap_event(&p_data->reconnect_ind);
      if (!mcap_mcl) {
        LOG(ERROR) << "No MCL for mcl_handle " << (int)mcl;
        break;
      }
      if (!mcap_mcl->IsConnected()) {
        LOG(ERROR) << "MCL " << (int)mcl << " not connected";
        break;
      }
      mcap_mdl = mcap_mcl->FindMdlById(p_data->reconnect_ind.mdl_id);
      if (mcap_mdl && !mcap_mdl->IsConnected()) {
        LOG(INFO) << "Creating reconnect response for MDL "
                  << (int)p_data->reconnect_ind.mdl_id;
        mcap_mdl->ReconnectResponse();
        break;
      }
      LOG_IF(WARNING, mcap_mdl && mcap_mdl->IsConnected())
          << "MDL ID " << (int)p_data->reconnect_ind.mdl_id
          << " is already connected";
      LOG_IF(WARNING, !mcap_mdl) << "No MDL for mdl_id "
                                 << p_data->reconnect_ind.mdl_id;
      tMCA_DEP mdep_handle = 0;
      if (_mdep_list.size() > 0) {
        mdep_handle = _mdep_list[0].GetHandle();
      } else {
        LOG(ERROR) << "Cannot find any available MDEP";
      }
      tMCA_RESULT ret = _mcap_test_interface->reconnect_mdl_response(
          mcl, mdep_handle, p_data->reconnect_ind.mdl_id, MCA_RSP_BAD_MDL,
          get_test_channel_config());
      LOG_IF(INFO, ret != MCA_SUCCESS) << "ret=" << ret;
      break;
    }
    case MCA_RECONNECT_CFM_EVT:
      // Called when MCA_ReconnectMdl step 1, receives a response
      print_mcap_event(&p_data->reconnect_cfm);
      if (!mcap_mcl) {
        LOG(ERROR) << "No MCL for mcl_handle " << (int)mcl;
        break;
      }
      if (!mcap_mcl->IsConnected()) {
        LOG(ERROR) << "MCL not connected for mcl_handle " << (int)mcl;
        break;
      }
      mcap_mdl = mcap_mcl->FindMdlById(p_data->reconnect_cfm.mdl_id);
      if (!mcap_mdl) {
        LOG(ERROR) << "MDL not found for id " << p_data->reconnect_cfm.mdl_id;
        break;
      }
      if (mcap_mdl->GetResponseCode() >= 0) {
        LOG(ERROR) << "MDL already got response " << mcap_mdl->GetResponseCode()
                   << " for id " << p_data->reconnect_cfm.mdl_id;
        break;
      }
      mcap_mdl->SetResponseCode(p_data->reconnect_cfm.rsp_code);
      break;

    case MCA_ABORT_IND_EVT:
      print_mcap_event(&p_data->abort_ind);
      if (!mcap_mcl) {
        LOG(ERROR) << "No MCL for mcl_handle " << (int)mcl;
        break;
      }
      if (!mcap_mcl->IsConnected()) {
        LOG(ERROR) << "MCL not connected for mcl_handle " << (int)mcl;
        break;
      }
      mcap_mdl = mcap_mcl->FindMdlById(p_data->abort_ind.mdl_id);
      if (!mcap_mdl) {
        LOG(ERROR) << "MDL not found for id " << (int)p_data->abort_ind.mdl_id;
        break;
      }
      if (mcap_mdl->IsConnected()) {
        LOG(ERROR) << "MDL is already connected for id "
                   << (int)p_data->abort_ind.mdl_id;
      }
      mcap_mcl->RemoveMdl(p_data->abort_ind.mdl_id);
      break;

    case MCA_ABORT_CFM_EVT:
      // Called when MCA_Abort succeeded
      print_mcap_event(&p_data->abort_cfm);
      if (!mcap_mcl) {
        LOG(ERROR) << "No MCL for mcl_handle " << (int)mcl;
        break;
      }
      if (!mcap_mcl->IsConnected()) {
        LOG(ERROR) << "MCL " << (int)mcl << " not connected";
        break;
      }
      mcap_mdl = mcap_mcl->FindMdlById(p_data->abort_cfm.mdl_id);
      if (!mcap_mdl) {
        LOG(ERROR) << "MDL not found for id " << (int)p_data->abort_cfm.mdl_id;
        break;
      }
      if (mcap_mdl->IsConnected()) {
        LOG(ERROR) << "MDL is already connected for id "
                   << (int)p_data->abort_cfm.mdl_id;
      }
      mcap_mcl->RemoveMdl(p_data->abort_cfm.mdl_id);
      break;

    case MCA_DELETE_IND_EVT:
      print_mcap_event(&p_data->delete_ind);
      if (!mcap_mcl) {
        LOG(ERROR) << "No MCL for mcl_handle " << (int)mcl;
        break;
      }
      if (!mcap_mcl->IsConnected()) {
        LOG(ERROR) << "MCL " << (int)mcl << " not connected";
        break;
      }
      if (p_data->delete_ind.mdl_id == MCA_ALL_MDL_ID) {
        mcap_mcl->RemoveAllMdl();
      } else {
        mcap_mcl->RemoveMdl(p_data->delete_ind.mdl_id);
      }
      break;

    case MCA_DELETE_CFM_EVT:
      // Called when MCA_Delete succeeded
      print_mcap_event(&p_data->delete_cfm);
      if (!mcap_mcl) {
        LOG(ERROR) << "No MCL for mcl_handle " << (int)mcl;
        break;
      }
      if (!mcap_mcl->IsConnected()) {
        LOG(ERROR) << "MCL " << (int)mcl << " not connected";
        break;
      }
      if (p_data->delete_cfm.rsp_code) {
        LOG(ERROR) << "No success response " << (int)p_data->delete_cfm.rsp_code
                   << " when deleting MDL_ID "
                   << (int)p_data->delete_cfm.mdl_id;
        break;
      }
      if (p_data->delete_cfm.mdl_id == MCA_ALL_MDL_ID) {
        mcap_mcl->RemoveAllMdl();
      } else {
        mcap_mcl->RemoveMdl(p_data->delete_cfm.mdl_id);
      }
      break;

    case MCA_CONNECT_IND_EVT: {
      // Called when MCA_ConnectReq succeeded
      print_mcap_event(&p_data->connect_ind);
      LOG(INFO) << "Received MCL handle " << (int)mcl;
      RawAddress bd_addr = p_data->connect_ind.bd_addr;
      mcap_mcl = FindMclByPeerAddress(bd_addr);
      if (!mcap_mcl) {
        LOG(INFO) << "Creating new MCL for ID " << (int)mcl;
        _mcl_list.push_back(
            McapMcl(_mcap_test_interface, _mcap_handle, bd_addr));
        mcap_mcl = &_mcl_list[_mcl_list.size() - 1];
      }
      if (mcap_mcl->IsConnected()) {
        LOG(ERROR) << "MCL is already connected for handle " << (int)mcl;
        break;
      }
      mcap_mcl->SetHandle(mcl);
      mcap_mcl->SetMtu(p_data->connect_ind.mtu);
      break;
    }
    case MCA_DISCONNECT_IND_EVT: {
      // Called when MCA_ConnectReq failed or MCA_DisconnectReq succeeded
      print_mcap_event(&p_data->disconnect_ind);
      RawAddress bd_addr = p_data->disconnect_ind.bd_addr;
      mcap_mcl = FindMclByPeerAddress(bd_addr);
      if (!mcap_mcl) {
        LOG(ERROR) << "No MCL for BD addr " << bd_addr;
        break;
      }
      if (!mcap_mcl->IsConnected()) {
        LOG(WARNING) << "MCL for " << bd_addr << " is already disconnected";
      }
      mcap_mcl->SetHandle(0);
      mcap_mcl->SetMtu(0);
      mcap_mcl->ResetAllMdl();
      break;
    }
    case MCA_OPEN_IND_EVT:
    // Called when MCA_CreateMdlRsp succeeded step 2, data channel is open
    // Called when MCA_ReconnectMdlRsp succeeded step 2, data channel is open
    case MCA_OPEN_CFM_EVT:
      // Called when MCA_CreateMdl succeeded step 2, data channel is open
      // Called when MCA_ReconnectMdl succeeded step 2, data channel is open
      // Called when MCA_DataChnlCfg succeeded
      print_mcap_event(&p_data->open_ind);
      if (!mcap_mcl) {
        LOG(ERROR) << "No MCL for mcl_handle " << (int)mcl;
        break;
      }
      if (!mcap_mcl->IsConnected()) {
        LOG(ERROR) << "MCL not connected for mcl_handle " << (int)mcl;
        break;
      }
      mcap_mdl = mcap_mcl->FindMdlById(p_data->open_ind.mdl_id);
      if (mcap_mdl) {
        if (mcap_mdl->IsConnected()) {
          LOG(ERROR) << "MDL is already connected for mcl_handle "
                     << (int)p_data->open_ind.mdl_id;
          break;
        }
        mcap_mdl->SetMtu(p_data->open_ind.mtu);
        mcap_mdl->SetHandle(p_data->open_ind.mdl);
      } else {
        LOG(ERROR) << "No MDL for mdl_id " << (int)p_data->reconnect_ind.mdl_id;
      }
      break;

    case MCA_CLOSE_IND_EVT:
    case MCA_CLOSE_CFM_EVT:
      // Called when MCA_CloseReq is successful
      print_mcap_event(&p_data->close_cfm);
      if (!mcap_mcl) {
        LOG(ERROR) << "No MCL for mcl_handle " << (int)mcl;
        break;
      }
      if (!mcap_mcl->IsConnected()) {
        LOG(ERROR) << "MCL not connected for mcl_handle " << (int)mcl;
        break;
      }
      mcap_mdl = mcap_mcl->FindMdlById(p_data->close_cfm.mdl_id);
      if (mcap_mdl) {
        mcap_mdl->SetMtu(0);
        mcap_mdl->SetHandle(0);
      } else {
        LOG(WARNING) << "No MDL for mdl_id " << (int)p_data->close_cfm.mdl_id;
      }
      break;

    case MCA_CONG_CHG_EVT:
      print_mcap_event(&p_data->cong_chg);
      if (!mcap_mcl) {
        LOG(ERROR) << "No MCL for mcl_handle " << (int)mcl;
        break;
      }
      if (!mcap_mcl->IsConnected()) {
        LOG(ERROR) << "MCL not connected for mcl_handle " << (int)mcl;
        break;
      }
      break;

    case MCA_RSP_TOUT_IND_EVT:
    case MCA_SYNC_CAP_IND_EVT:
    case MCA_SYNC_CAP_CFM_EVT:
    case MCA_SYNC_SET_IND_EVT:
    case MCA_SYNC_SET_CFM_EVT:
    case MCA_SYNC_INFO_IND_EVT:
      print_mcap_event(&p_data->hdr);
      break;
  }
}

}  // namespace SYSTEM_BT_TOOLS_MCAP_TOOL