/*
 * Copyright (c) 2017 Cisco and/or its affiliates.
 * 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 "vom/hw.hpp"
#include "vom/hw_cmds.hpp"
#include "vom/logger.hpp"

namespace VOM {
HW::cmd_q::cmd_q()
  : m_enabled(true)
  , m_connected(false)
  , m_conn()
{
}

HW::cmd_q::~cmd_q()
{
}

HW::cmd_q&
HW::cmd_q::operator=(const HW::cmd_q& f)
{
  return (*this);
}

/**
 * Run the connect/dispatch thread.
 */
void
HW::cmd_q::rx_run()
{
  while (m_connected) {
    m_conn.ctx().dispatch();
  }
}

void
HW::cmd_q::enqueue(cmd* c)
{
  std::shared_ptr<cmd> sp(c);

  m_queue.push_back(sp);
}

void
HW::cmd_q::enqueue(std::shared_ptr<cmd> c)
{
  m_queue.push_back(c);
}

void
HW::cmd_q::enqueue(std::queue<cmd*>& cmds)
{
  while (cmds.size()) {
    std::shared_ptr<cmd> sp(cmds.front());

    m_queue.push_back(sp);
    cmds.pop();
  }
}

bool
HW::cmd_q::connect()
{
  int rv;

  if (m_connected)
    return m_connected;

  rv = m_conn.connect();

  m_connected = true;
  m_rx_thread.reset(new std::thread(&HW::cmd_q::rx_run, this));
  return (rv == 0);
}

void
HW::cmd_q::disconnect()
{

  if (!m_connected)
    return;

  m_connected = false;

  if (m_rx_thread && m_rx_thread->joinable()) {
    m_rx_thread->join();
  }

  m_conn.disconnect();
}

void
HW::cmd_q::enable()
{
  m_enabled = true;
}

void
HW::cmd_q::disable()
{
  m_enabled = false;
}

rc_t
HW::cmd_q::write()
{
  rc_t rc = rc_t::OK;

  /*
   * The queue is enabled, Execute each command in the queue.
   * If one execution fails, abort the rest
   */
  auto it = m_queue.begin();

  while (it != m_queue.end()) {
    std::shared_ptr<cmd> c = *it;

    VOM_LOG(log_level_t::DEBUG) << *c;

    if (m_enabled) {
      /*
       * before we issue the command we must move it to the pending
       * store
       * ince a async event can be recieved before the command
       * completes
       */
      rc = c->issue(m_conn);

      if (rc_t::OK == rc) {
        /*
         * move to the next
         */
      } else {
        /*
         * barf out without issuing the rest
         */
        VOM_LOG(log_level_t::ERROR) << "Failed to execute: " << c->to_string();
        break;
      }
    } else {
      /*
       * The HW is disabled, so set each command as succeeded
       */
      c->succeeded();
    }

    ++it;
  }

  /*
   * erase all objects in the queue
   */
  m_queue.erase(m_queue.begin(), m_queue.end());

  return (rc);
}

/*
 * The single Command Queue
 */
HW::cmd_q* HW::m_cmdQ;
HW::item<bool> HW::m_poll_state;

/**
 * Initialse the connection to VPP
 */
void
HW::init(HW::cmd_q* f)
{
  m_cmdQ = f;
}

/**
 * Initialse the connection to VPP
 */
void
HW::init()
{
  m_cmdQ = new cmd_q();
}

void
HW::enqueue(cmd* cmd)
{
  m_cmdQ->enqueue(cmd);
}

void
HW::enqueue(std::shared_ptr<cmd> cmd)
{
  m_cmdQ->enqueue(cmd);
}

void
HW::enqueue(std::queue<cmd*>& cmds)
{
  m_cmdQ->enqueue(cmds);
}

bool
HW::connect()
{
  return m_cmdQ->connect();
}

void
HW::disconnect()
{
  m_cmdQ->disconnect();
}

void
HW::enable()
{
  m_cmdQ->enable();
}

void
HW::disable()
{
  m_cmdQ->disable();
}

rc_t
HW::write()
{
  return (m_cmdQ->write());
}

bool
HW::poll()
{
  std::shared_ptr<cmd> poll(new hw_cmds::poll(m_poll_state));

  HW::enqueue(poll);
  HW::write();

  return (m_poll_state);
}

template <>
std::string
HW::item<bool>::to_string() const
{
  std::ostringstream os;

  os << "hw-item:["
     << "rc:" << item_rc.to_string() << " data:" << item_data << "]";
  return (os.str());
}

template <>
std::string
HW::item<unsigned int>::to_string() const
{
  std::ostringstream os;

  os << "hw-item:["
     << "rc:" << item_rc.to_string() << " data:" << item_data << "]";
  return (os.str());
}
}

/*
 * fd.io coding-style-patch-verification: ON
 *
 * Local Variables:
 * eval: (c-set-style "mozilla")
 * End:
 */