/*
 * The CERN Tape Archive(CTA) project
 * Copyright(C) 2015  CERN
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 *(at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "common/utils/utils.hpp"
#include "common/SmartFd.hpp"
#include "mediachanger/CommonMarshal.hpp"
#include "mediachanger/io.hpp"
#include "mediachanger/RmcMarshal.hpp"
#include "mediachanger/RmcProxy.hpp"
#include "mediachanger/ScsiLibrarySlot.hpp"

namespace cta {
namespace mediachanger {

//------------------------------------------------------------------------------
// constructor
//------------------------------------------------------------------------------
RmcProxy::RmcProxy(
  const unsigned short rmcPort,
  const int netTimeout,
  const unsigned int maxRqstAttempts) throw():
  m_rmcPort(rmcPort),
  m_netTimeout(netTimeout),
  m_maxRqstAttempts(maxRqstAttempts) {
} 

//------------------------------------------------------------------------------
// destructor
//------------------------------------------------------------------------------
RmcProxy::~RmcProxy() throw() {
}

//------------------------------------------------------------------------------
// mountTapeReadOnly
//------------------------------------------------------------------------------
void RmcProxy::mountTapeReadOnly(const std::string &vid, const LibrarySlot &librarySlot) {
  // SCSI libraries do not support read-only mounts
  mountTapeReadWrite(vid, librarySlot);
}

//------------------------------------------------------------------------------
// mountTapeReadWrite
//------------------------------------------------------------------------------
void RmcProxy::mountTapeReadWrite(const std::string &vid, const LibrarySlot &librarySlot) {
  try {
    RmcMountMsgBody rqstBody;
    rqstBody.uid = geteuid();
    rqstBody.gid = getegid();
    utils::copyString(rqstBody.vid, vid);
    const ScsiLibrarySlot &scsiLibrarySlot = dynamic_cast<const ScsiLibrarySlot&>(librarySlot);
    rqstBody.drvOrd = scsiLibrarySlot.getDrvOrd();

    rmcSendRecvNbAttempts(m_maxRqstAttempts, rqstBody);
  } catch(cta::exception::Exception &ne) {
    cta::exception::Exception ex;
    ex.getMessage() <<
      "Failed to mount tape in SCSI tape-library for read/write access"
      ": vid=" << vid << " librarySlot=" << librarySlot.str() << ": " <<
      ne.getMessage().str();
    throw ex;
  }
}

//------------------------------------------------------------------------------
// dismountTape
//------------------------------------------------------------------------------
void RmcProxy::dismountTape(const std::string &vid, const LibrarySlot &librarySlot) {
  try {
    RmcUnmountMsgBody rqstBody;
    rqstBody.uid = geteuid();
    rqstBody.gid = getegid();
    utils::copyString(rqstBody.vid, vid);
    const ScsiLibrarySlot &scsiLibrarySlot = dynamic_cast<const ScsiLibrarySlot&>(librarySlot);
    rqstBody.drvOrd = scsiLibrarySlot.getDrvOrd();
    rqstBody.force = 0;

    rmcSendRecvNbAttempts(m_maxRqstAttempts, rqstBody);
  } catch(cta::exception::Exception &ne) {
    cta::exception::Exception ex;
    ex.getMessage() <<
      "Failed to dismount tape in SCSI tape-library"
      ": vid=" << vid << " librarySlot=" << librarySlot.str() << ": " <<
      ne.getMessage().str();
    throw ex;
  }
}

//------------------------------------------------------------------------------
// forceDismountTape
//------------------------------------------------------------------------------
void RmcProxy::forceDismountTape(const std::string &vid, const LibrarySlot &librarySlot) {
  // SCSI libraries do not support forced dismounts
  dismountTape(vid, librarySlot);
}

//-----------------------------------------------------------------------------
// connectToRmc
//-----------------------------------------------------------------------------
int RmcProxy::connectToRmc()
  const {
  const std::string rmcHost = "localhost";
  cta::SmartFd smartConnectSock;
  try {
    smartConnectSock.reset(connectWithTimeout(rmcHost, m_rmcPort,
      m_netTimeout));
  } catch(cta::exception::Exception &ne) {
    cta::exception::Exception ex;
    ex.getMessage() << "Failed to connect to rmcd: rmcHost=" << rmcHost
      << " rmcPort=" << RMC_PORT << ": " << ne.getMessage().str();
    throw ex;
  }

  return smartConnectSock.release();
}

//-----------------------------------------------------------------------------
// writeRmcMountMsg
//-----------------------------------------------------------------------------
void RmcProxy::writeRmcMountMsg(const int fd, const RmcMountMsgBody &body) {
  char buf[RMC_MSGBUFSIZ];
  const size_t len = marshal(buf, body);

  try {
    writeBytes(fd, m_netTimeout, len, buf);
  } catch(cta::exception::Exception &ne) {
    cta::exception::Exception ex;
    ex.getMessage() << "Failed to write RMC_SCSI_MOUNT message: "
      << ne.getMessage().str();
    throw ex;
  }
}

//-----------------------------------------------------------------------------
// readRmcMsgHeader
//-----------------------------------------------------------------------------
MessageHeader RmcProxy::readRmcMsgHeader(const int fd) {
  char buf[12]; // Magic + type + len
  MessageHeader header;

  try {
    readBytes(fd, m_netTimeout, sizeof(buf), buf);
  } catch(cta::exception::Exception &ne) {
    cta::exception::Exception ex;
    ex.getMessage() << "Failed to read message header: "
      << ne.getMessage().str();
    throw ex;
  }

  const char *bufPtr = buf;
  size_t bufLen = sizeof(buf);
  unmarshal(bufPtr, bufLen, header);

  if(RMC_MAGIC != header.magic) {
    cta::exception::Exception ex;
    ex.getMessage() << "Failed to read message header: "
      " Header contains an invalid magic number: expected=0x" << std::hex <<
      RMC_MAGIC << " actual=0x" << header.magic;
    throw ex;
  }

  return header;
}

//-----------------------------------------------------------------------------
// writeRmcUnmountMsg
//-----------------------------------------------------------------------------
void RmcProxy::writeRmcUnmountMsg(const int fd, const RmcUnmountMsgBody &body) {
  char buf[RMC_MSGBUFSIZ];
  const size_t len = marshal(buf, body);

  try {
    writeBytes(fd, m_netTimeout, len, buf);
  } catch(cta::exception::Exception &ne) {
    cta::exception::Exception ex;
    ex.getMessage() << "Failed to write RMC_SCSI_UNMOUNT message: "
      << ne.getMessage().str();
    throw ex;
  }
}

//-----------------------------------------------------------------------------
// rmcReplyTypeToStr
//-----------------------------------------------------------------------------
std::string RmcProxy::rmcReplyTypeToStr(const int replyType) {
  std::ostringstream oss;
  switch(replyType) {
  case RMC_RC:
    oss << "RMC_RC";
    break;
  case MSG_ERR:
    oss << "MSG_ERR";
    break;
  default:
    oss << "UNKNOWN(0x" << std::hex << replyType << ")";
  }
  return oss.str();
}

//-----------------------------------------------------------------------------
// handleMSG_ERR
//-----------------------------------------------------------------------------
std::string RmcProxy::handleMSG_ERR(const MessageHeader &header, const int fd) {
  char errorBuf[1024];
  const int nbBytesToRead = header.lenOrStatus > sizeof(errorBuf) ?
    sizeof(errorBuf) : header.lenOrStatus;
  readBytes(fd, m_netTimeout, nbBytesToRead, errorBuf);
  errorBuf[sizeof(errorBuf) - 1] = '\0';
  return errorBuf;
}

} // namespace mediachanger
} // namespace cta