/*

Copyright (C) 2019 Olaf Till <i7tiol@t-online.de>

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

*/

#ifndef __OCT_PARALLEL_PIPES__

#define __OCT_PARALLEL_PIPES__

#include "config.h"

#include <octave/defaults.h>
#include <octave/oct-syscalls.h>

#include <unistd.h>

#include <stdint.h>

#if defined _WIN32 && ! defined __CYGWIN__
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif

namespace octave_parallel
{
  // This class handles the differences between Unix pipes and Windows
  // native pipes.
  class pipes
  {

  public:

    class pipe_wrapper
    {
      friend class pipes;

    public:
#if defined _WIN32 && ! defined __CYGWIN__
      typedef HANDLE native_pd_t;

      pipe_wrapper (void) : pd (NULL), posix_pd (-1)
      {

      }
#else
      typedef int native_pd_t;

      pipe_wrapper (void) : pd (-1)
      {

      }
#endif

      pipe_wrapper (uint64_t pd_arg) : pd ((native_pd_t)pd_arg)
      {
#if defined _WIN32 && ! defined __CYGWIN__
        posix_pd = _open_osfhandle ((intptr_t)pd, 0);
#endif
      }

      // prevent copying, the Windows HANDLE may be unsafe to double
      // close
      pipe_wrapper (const pipe_wrapper&) = delete;
      pipe_wrapper& operator= (const pipe_wrapper&) = delete;

      ~pipe_wrapper (void)
      {
        close ();
      }

      int close (void)
      {
        int ret;

        if (pd_valid ())
          {
#if defined _WIN32 && ! defined __CYGWIN__

            if (! _close (posix_pd)) // closes underlying HANDLE pd, too
              {
                set_pd_invalid ();

                ret = 0;
              }
            else
              ret = -1;

#else // Unix API

            ret = ::close (pd);

            if (! ret)
              set_pd_invalid ();

#endif
          }
        else
          ret = -1;

        return ret;
      }

      octave_idx_type write (const void *buf, octave_idx_type count)
      {
#if defined _WIN32 && ! defined __CYGWIN__

        unsigned long have_written;

        if (WriteFile (pd, (LPCVOID) buf, count, &have_written, NULL))
          {
            octave_idx_type ret = have_written;

            // check for overflow
            if (ret < 0
                || (unsigned long) ret != have_written)
              {
                // FIXME: this could be prevented by restricting write
                // count
                return -1;
              }

            return ret;
          }
        else
          return -1;

#else // Unix API

        return ::write (pd, buf, count);

#endif
      }

      octave_idx_type read (void *buf, octave_idx_type count)
      {
#if defined _WIN32 && ! defined __CYGWIN__

        unsigned long have_read;

        if (ReadFile (pd, (LPVOID) buf, count, &have_read, NULL))
          {
            octave_idx_type ret = have_read;

            // check for overflow
            if (ret < 0
                || (unsigned long) ret != have_read)
              {
                // FIXME: this could be prevented by restricting read
                // count
                return -1;
              }

            return ret;
          }
        else
          return -1;  

#else // Unix API

        return ::read (pd, buf, count);

#endif
      }

    private:

      native_pd_t pd;

#if defined _WIN32 && ! defined __CYGWIN__

      int posix_pd; // for polling

      bool pd_valid (void)
      {
        return pd;
      }

      void set_pd_invalid (void)
      {
        pd = NULL;
      }

#else

      bool pd_valid (void)
      {
        return pd >= 0;
      }

      void set_pd_invalid (void)
      {
        pd = -1;
      }      

#endif

      void set_pd (const native_pd_t pd_arg)
      {
        pd = pd_arg;

#if defined _WIN32 && ! defined __CYGWIN__
        posix_pd = _open_osfhandle ((intptr_t)pd, 0);
#endif
      }
    };
    
    pipes (void) :
      store_errno (0),
      out_read (), out_write (), in_read (), in_write ()
    {
#if defined _WIN32 && ! defined __CYGWIN__

      // be careful to prevent leaks by calling .set_pd () immediately 

      HANDLE t_read, t_write, current_process = GetCurrentProcess ();
      if (CreatePipe (&t_read, &t_write, 0, 0))
        {
          out_read.set_pd (t_read);
          out_write.set_pd (t_write);

          if (DuplicateHandle (current_process, t_read,
                               current_process, &t_read,
                               0, TRUE,
                               DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE))
            out_read.set_pd (t_read);
          else
            store_errno = -1;
        }
      else
        store_errno = - 1;

      if (! good ())
        return;

      if (CreatePipe (&t_read, &t_write, 0, 0))
        {
          in_read.set_pd (t_read);
          in_write.set_pd (t_write);

          if (DuplicateHandle (current_process, t_write,
                               current_process, &t_write,
                               0, TRUE,
                               DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE))
            in_write.set_pd (t_write);
          else
            store_errno = -1;
        }
      else
        store_errno = - 1;

#else

      int t_pds [2];
      if (::pipe (t_pds))
        {
          store_errno = errno;
          return;
        }
      else
        {
          out_read.set_pd (t_pds[0]);
          out_write.set_pd (t_pds[1]);
        }
      if (::pipe (t_pds))
        {
          store_errno = errno;
          return;
        }
      else
        {
          in_read.set_pd (t_pds[0]);
          in_write.set_pd (t_pds[1]);
        }

#endif
    }

    ~pipes (void)
    {

    }

    int close_child_side (void)
    {
      if (out_read.close () || in_write.close ())
        {
          store_errno = -1;

          return store_errno;
        }
      else
        return 0;
    }

    int close_parent_side (void)
    {
      if (out_write.close () || in_read.close ())
        {
          store_errno = -1;

          return store_errno;
        }
      else
        return 0;
    }

    pipe_wrapper& get_out_write (void)
    {
      return out_write;
    }

    pipe_wrapper& get_in_read (void)
    {
      return in_read;
    }

    pipe_wrapper::native_pd_t get_out_read_handle (void)
    {
      return out_read.pd;
    }

    pipe_wrapper::native_pd_t get_in_write_handle (void)
    {
      return in_write.pd;
    }

    int get_out_write_posix_fd (void)
    {
#if defined _WIN32 && ! defined __CYGWIN__
      return out_write.posix_pd;
#else
      return out_write.pd;
#endif
    }

    int get_in_read_posix_fd (void)
    {
#if defined _WIN32 && ! defined __CYGWIN__
      return in_read.posix_pd;
#else
      return in_read.pd;
#endif
    }

    bool good (void)
    {
      return store_errno == 0;
    }

  private:
    
    int store_errno;

    pipe_wrapper out_read;
    pipe_wrapper out_write;
    pipe_wrapper in_read;
    pipe_wrapper in_write;
  };

} // namespace octave_parallel

#endif // __OCT_PARALLEL_PIPES__
