/*
 * Copyright (C) 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 <cstring>

#include <base/logging.h>

#include "mca_api.h"
#include "mca_defs.h"
#include "mcap_test_mcl.h"

namespace SYSTEM_BT_TOOLS_MCAP_TOOL {

McapMcl::McapMcl(btmcap_test_interface_t* mcap_test_interface,
                 tMCA_HANDLE mcap_handle, const RawAddress& peer_bd_addr)
    : _mdl_list() {
  _mcap_handle = mcap_handle;
  _mcap_test_interface = mcap_test_interface;
  memcpy(_peer_bd_addr.address, peer_bd_addr.address,
         sizeof(_peer_bd_addr.address));
}

bool McapMcl::Connect(uint16_t ctrl_psm, uint16_t sec_mask) {
  tMCA_RESULT ret = _mcap_test_interface->connect_mcl(
      _mcap_handle, _peer_bd_addr, ctrl_psm, sec_mask);
  LOG_IF(INFO, ret != MCA_SUCCESS) << "ret=" << (int)ret;
  return ret == MCA_SUCCESS;
}

bool McapMcl::Disconnect() {
  if (!IsConnected()) {
    LOG(ERROR) << "MCL is not connected";
    return false;
  }
  tMCA_RESULT ret = _mcap_test_interface->disconnect_mcl(_mcl_handle);
  LOG_IF(INFO, ret != MCA_SUCCESS) << "ret=" << (int)ret;
  return ret == MCA_SUCCESS;
}

McapMdl* McapMcl::AllocateMdl(tMCA_DEP mdep_handle, uint16_t mdl_id,
                              uint8_t dep_id, uint8_t cfg) {
  if (!IsConnected()) {
    LOG(ERROR) << "MCL is not connected";
    return nullptr;
  }
  if (FindMdlById(mdl_id) != nullptr) {
    LOG(ERROR) << "mdl_id=" << mdl_id << "already exists";
    return nullptr;
  }
  if (!HasAvailableMdl()) {
    LOG(ERROR) << "No more avaible MDL, currently " << _mdl_list.size();
    return nullptr;
  }
  _mdl_list.push_back(McapMdl(_mcap_test_interface, _mcl_handle, mdep_handle,
                              mdl_id, dep_id, cfg));
  return &_mdl_list[_mdl_list.size() - 1];
}

bool McapMcl::CreateMdl(tMCA_DEP mdep_handle, uint16_t data_psm,
                        uint16_t mdl_id, uint8_t peer_dep_id, uint8_t cfg,
                        bool should_connect) {
  if (!IsConnected()) {
    LOG(ERROR) << "MCL is not connected";
    return false;
  }
  McapMdl* mcap_mdl = FindMdlById(mdl_id);
  if (!mcap_mdl) {
    LOG(INFO) << "mdl_id=" << mdl_id << "does not exists, creating new one";
    mcap_mdl = AllocateMdl(mdep_handle, mdl_id, peer_dep_id, cfg);
    if (!mcap_mdl) {
      return false;
    }
  }
  if (mcap_mdl->IsConnected()) {
    LOG(ERROR) << "mdl_id=" << mdl_id << "is already connected with handle "
               << (int)mcap_mdl->GetHandle();
    return false;
  }
  return mcap_mdl->Create(data_psm, should_connect);
}

bool McapMcl::DataChannelConfig() {
  tMCA_RESULT ret = _mcap_test_interface->data_channel_config(
      _mcl_handle, get_test_channel_config());
  LOG_IF(INFO, ret != MCA_SUCCESS) << "ret=" << (int)ret;
  return ret == MCA_SUCCESS;
}

bool McapMcl::CreateMdlResponse(tMCA_DEP mdep_handle, uint16_t mdl_id,
                                uint8_t my_dep_id, uint8_t cfg) {
  if (!IsConnected()) {
    LOG(ERROR) << "MCL is not connected";
    return false;
  }
  McapMdl* mcap_mdl = FindMdlById(mdl_id);
  if (!mcap_mdl) {
    LOG(INFO) << "mdl_id=" << mdl_id << " does not exists, creating new one";
    mcap_mdl = AllocateMdl(mdep_handle, mdl_id, my_dep_id, cfg);
    if (!mcap_mdl) {
      LOG(ERROR) << "MDL cannot be created";
      return false;
    }
  }
  if (mcap_mdl->IsConnected()) {
    LOG(INFO) << "mdl_id=" << mdl_id << " is already connected with handle "
              << (int)mcap_mdl->GetHandle() << ", updating context";
    mcap_mdl->UpdateContext(mdep_handle, my_dep_id, cfg);
  }
  return mcap_mdl->CreateResponse();
}

bool McapMcl::AbortMdl() {
  tMCA_RESULT ret = _mcap_test_interface->abort_mdl(_mcl_handle);
  LOG_IF(INFO, ret != MCA_SUCCESS) << "ret=" << (int)ret;
  return ret == MCA_SUCCESS;
}

bool McapMcl::DeleteMdl(uint16_t mdl_id) {
  tMCA_RESULT ret = _mcap_test_interface->delete_mdl(_mcl_handle, mdl_id);
  LOG_IF(INFO, ret != MCA_SUCCESS) << "ret=" << (int)ret;
  return ret == MCA_SUCCESS;
}

RawAddress& McapMcl::GetPeerAddress() { return _peer_bd_addr; }

void McapMcl::SetHandle(tMCA_CL handle) { _mcl_handle = handle; }

tMCA_CL McapMcl::GetHandle() const { return _mcl_handle; }

void McapMcl::SetMtu(uint16_t mtu) { _control_mtu = mtu; }

uint16_t McapMcl::GetMtu() { return _control_mtu; }

McapMdl* McapMcl::FindMdlById(uint16_t mdl_id) {
  for (McapMdl& mdl : _mdl_list) {
    if (mdl.GetId() == mdl_id) {
      return &mdl;
    }
  }
  return nullptr;
}

McapMdl* McapMcl::FindMdlByHandle(tMCA_DL mdl_handle) {
  for (McapMdl& mdl : _mdl_list) {
    if (mdl.GetHandle() == mdl_handle) {
      return &mdl;
    }
  }
  return nullptr;
}

void McapMcl::RemoveAllMdl() { _mdl_list.clear(); }

void McapMcl::RemoveMdl(uint16_t mdl_id) {
  LOG(INFO) << "Removing MDL id " << (int)mdl_id;
  for (std::vector<McapMdl>::iterator it = _mdl_list.begin();
       it != _mdl_list.end(); ++it) {
    if (it->GetId() == mdl_id) {
      _mdl_list.erase(it);
      LOG(INFO) << "Removed MDL id " << (int)mdl_id;
      return;
    }
  }
}

void McapMcl::ResetAllMdl() {
  for (McapMdl& mcap_mdl : _mdl_list) {
    mcap_mdl.SetHandle(0);
    mcap_mdl.SetMtu(0);
    mcap_mdl.SetResponseCode(-1);
  }
}

void McapMcl::ResetMdl(uint16_t mdl_id) {
  LOG(INFO) << "Closing MDL id " << (int)mdl_id;
  McapMdl* mcap_mdl = FindMdlById(mdl_id);
  if (!mcap_mdl) {
    LOG(ERROR) << "Cannot find MDL for id " << (int)mdl_id;
    return;
  }
  if (mcap_mdl->IsConnected()) {
    LOG(ERROR) << "MDL " << (int)mdl_id << " is still connected";
    return;
  }
  mcap_mdl->SetHandle(0);
  mcap_mdl->SetMtu(0);
  mcap_mdl->SetResponseCode(-1);
}

bool McapMcl::IsConnected() { return _mcl_handle > 0; }

int McapMcl::ConnectedMdlCount() {
  int count = 0;
  for (McapMdl& mcap_mdl : _mdl_list) {
    if (mcap_mdl.IsConnected()) {
      count++;
    }
  }
  return count;
}

bool McapMcl::HasAvailableMdl() { return ConnectedMdlCount() < MCA_NUM_MDLS; }

}  // namespace SYSTEM_BT_TOOLS_MCAP_TOOL
