/*
  This file is part of CDO. CDO is a collection of Operators to
  manipulate and analyse Climate model Data.

  Copyright (C) 2003-2020 Uwe Schulzweida, <uwe.schulzweida AT mpimet.mpg.de>
  See COPYING file for copying and redistribution conditions.

  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; version 2 of the License.

  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.
*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <cdi.h>

#include "pipeStream.h"
#include "cdo_output.h"

#ifdef HAVE_LIBPTHREAD

#include "pthread_debug.h"

PipeStream::PipeStream(int p_processID)
{
  ispipe = true;
  m_pipe = std::make_shared<pipe_t>();
  m_pipe->pipeSetName(p_processID, m_cdoStreamID);
  m_name = m_pipe->name;
}

int
PipeStream::openRead()
{
  rthreadID = pthread_self();
  isopen = true;
  return m_cdoStreamID;
}

int
PipeStream::openWrite(int filetype)
{
  Debug(PIPE_STREAM, "pipe %s", m_pipe->name);

  wthreadID = pthread_self();
  m_filetype = filetype;
  isopen = true;

  return m_cdoStreamID;
}

int
PipeStream::openAppend()
{
  cdoWarning("Operator does not support pipes!");
  return -1;
}

int
PipeStream::inqVlist()
{
  // m_vlist is changed when the whlist was successfully defined by another pipe!!
  auto vlistID = m_pipe->pipeInqVlist(m_vlistID);
  if (vlistID == -1) cdoAbort("Couldn't read data from input stream %s!", m_name.c_str());
  return vlistID;
}

void
PipeStream::defVlist(int p_vlistID)
{
  Debug(PIPE_STREAM, "%s pstreamID %d", m_pipe->name, m_cdoStreamID);
  auto vlistIDcp = vlistDuplicate(p_vlistID);
  m_pipe->pipeDefVlist(m_vlistID, vlistIDcp);
}

void
PipeStream::inqRecord(int *varID, int *levelID)
{
  m_pipe->pipeInqRecord(varID, levelID);
}

void
PipeStream::defRecord(int varID, int levelID)
{
  m_pipe->pipeDefRecord(varID, levelID);
}

void
PipeStream::readRecord(float *p_data, size_t *p_nmiss)
{
  m_nvals += m_pipe->pipeReadRecord(m_vlistID, p_data, p_nmiss);
}

void
PipeStream::readRecord(double *p_data, size_t *p_nmiss)
{
  m_nvals += m_pipe->pipeReadRecord(m_vlistID, p_data, p_nmiss);
}

void
PipeStream::readRecord(Field *p_field, size_t *p_nmiss)
{
  m_nvals += m_pipe->pipeReadRecord(m_vlistID, p_field, p_nmiss);
}

void
PipeStream::writeRecord(float *p_data, size_t p_nmiss)
{
  m_pipe->pipeWriteRecord(p_data, p_nmiss);
}

void
PipeStream::writeRecord(double *p_data, size_t p_nmiss)
{
  m_pipe->pipeWriteRecord(p_data, p_nmiss);
}

void
PipeStream::writeRecord(Field *p_field, size_t p_nmiss)
{
  m_pipe->pipeWriteRecord(p_field, p_nmiss);
}

void
PipeStream::copyRecord(CdoStreamID p_destination)
{
  (void) p_destination;
  // Not implemented for pipes
  // Cdi handles this. And in cdo we would have to decompress and recompress for copy operations
  // which is very resource intensive (also lossy compression)
  cdoWarning("Copy Record not possible with piped streams");
}

int
PipeStream::inqTimestep(int p_tsID)
{
  auto nrecs = m_pipe->pipeInqTimestep(p_tsID);
  m_tsID = p_tsID;
  Debug(PIPE_STREAM, "PipeStream: Current TsID: %d,  nrecs: %d", m_tsID, nrecs);
  return nrecs;
}

void
PipeStream::defTimestep(int p_tsID)
{
  Debug(PIPE_STREAM, "%s pstreamID %d", m_pipe->name, m_cdoStreamID);
  m_pipe->pipeDefTimestep(m_vlistID, p_tsID);
}

int
PipeStream::inqFileType()
{
  return m_filetype;
}

int
PipeStream::inqByteorder()
{
  return m_filetype;
}

void
PipeStream::waitForPipe()
{
  m_pipe->close();
  std::unique_lock<std::mutex> locked_mutex(m_pipe->m_mutex);
  while (isopen)
    {
      Debug(PIPE_STREAM, "wait of read close");
      m_pipe->isClosed_cond.wait(locked_mutex);
    }
}

void
PipeStream::close()
{
  auto threadID = pthread_self();

  Debug(PIPE_STREAM, "thID: %d rthID: %d wthID: %d", threadID, rthreadID, wthreadID);

  if (pthread_equal(threadID, rthreadID))
    {
      isopen = false;
      m_pipe->close();
      pthread_join(wthreadID, nullptr);
    }
  else if (pthread_equal(threadID, wthreadID))
    {
      waitForPipe();
    }
  else
    {
      cdoAbort("Internal problem! Close pipe %s", m_name);
    }
}

size_t
PipeStream::getNvals()
{
  return m_nvals;
}

#endif
