%% Copyright (C) 2014-2016, 2022, 2024 Colin B. Macdonald
%% Copyright (C) 2022 Alex Vong
%%
%% This file is part of OctSymPy.
%%
%% OctSymPy 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 software 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 software; see the file COPYING.
%% If not, see <http://www.gnu.org/licenses/>.

%% -*- texinfo -*-
%% @deftypefun {[@var{A}, @var{info}] =} python_ipc_system (@dots{})
%% Private helper function for Python IPC.
%%
%% @var{A} is the resulting object, which might be an error code.
%%
%% @var{info} usually contains diagnostics to help with debugging
%% or error reporting.
%%
%% @code{@var{info}.prelines}: the number of lines of header code
%% before the command starts.
%%
%% @code{@var{info}.raw}: the raw output, for debugging.
%% @end deftypefun

function [A, info] = python_ipc_system(what, cmd, mktmpfile, varargin)

  persistent first_time
  persistent tmpfilename

  info = [];

  if (strcmp(what, 'reset'))
    show_msg = [];
    A = true;
    return
  end

  if ~(strcmp(what, 'run'))
    error('unsupported command')
  end

  verbose = ~sympref('quiet');

  if (isempty(first_time))
    first_time = true;
  end

  if (verbose && first_time)
    fprintf ('Symbolic pkg v%s: using slower system() communications with SymPy.\n', ...
             sympref ('version'))
    disp('Warning: this will be *SLOW*.  Every round-trip involves executing a')
    disp('new python process and many operations involve several round-trips.')
  end

  newl = sprintf('\n');

  %% Headers
  headers = python_header();

  %% load all the inputs into python as pickles
  s_in = python_copy_vars_to('_ins', true, varargin{:});

  % the number of lines of code before the command itself
  info.prelines = numel(strfind(headers, newl)) + numel(strfind(s_in, newl));


  %% output, or perhaps a thrown error
  s_out = python_copy_vars_from('_outs');

  cmd = [cmd '_outs = _fcn(_ins)'];
  % join all the cell arrays with newlines
  s = strjoin([s_in cmd s_out], newl);

  pyexec = sympref('python');
  if (first_time)
    assert_have_python_and_sympy (pyexec)
  end

  %% FIXME: Issue #63: with new regexp code on Matlab
  % workaround:
  % sympref python 'LD_LIBRARY_PATH="" python'
  % to prevent a conflict with the expat shipped with Matlab 2014a
  % See here with oracle
  % https://bugzilla.redhat.com/show_bug.cgi?id=821337
  % FIXME: make this the default on Matlab install?

  bigs = [headers s];

  if (~mktmpfile)
    %% paste all the commands into the system() command line
    % escaping: cmd or inputs might have \"
    % " -> \"
    % \" -> \\\"
    % \n -> \\n
    bigs = strrep(bigs, '\', '\\');
    bigs = strrep(bigs, '"', '\"');
    [status,out] = system([pyexec ' -c "' bigs '"']);
  else
    %% Generate a temp .py file then execute it with system()
    % can be useful for debugging, or if "python -c" fails for you
    if (isempty (tmpfilename))
      tmpfilename = [tempname() '_octsympytmp.py'];
      if (verbose)
        disp (['This session will use the temp file "' tmpfilename '"'])
      end
    end
    [fd, msg] = fopen (tmpfilename, 'w');
    if fd < 0
      error ('system ipc: failed to create temporary file %s: %s', ...
             tmpfilename, msg);
    end
    fprintf(fd, '# temporary autogenerated code\n\n');
    % we just added two more lines at the top
    info.prelines = info.prelines + 2;
    status = fputs(fd, bigs);
    if status < 0
      error ('system ipc: failed to write the big string to %s (fd %d)', ...
	     tmpfilename, fd);
    end
    status = fclose(fd);
    if status < 0
      error ('system ipc: failed to close %s (fd %d) after writing', ...
	     tmpfilename, fd);
    end

    % On Windows, tmp file is like C:\Users\Zoe\AppData\Local\Temp\oct-rZDdea
    % and this causes issues for msys2 [1].  It seems the same files are available
    % in /tmp/ (?) and cygpath translates them, but careful not to change the
    % persistent variable [2].
    % [1] https://github.com/gnu-octave/symbolic/issues/1182
    % [2] https://savannah.gnu.org/bugs/index.php?65735
    if python_env_is_cygwin_like (pyexec)
      _tmpfilename = cygpath (tmpfilename);
    else
      _tmpfilename = tmpfilename;
    end
    [status, out] = system ([pyexec ' ' _tmpfilename]);
  end

  info.raw = out;

  % two blocks if everything worked, one on variable import fail
  ind = strfind(out, '<output_block>');

  if (status ~= 0) && isempty(ind)
    status
    out
    ind
    error('system ipc: system() call failed!');
  end

  A = extractblock(out(ind(1):end));
  if (ischar(A) && strcmp(A, 'PYTHON: successful variable import'))
    % pass
  elseif (iscell(A) && strcmp(A{1}, 'INTERNAL_PYTHON_ERROR'))
    return
  else
    A
    out
    error('system ipc: something unexpected happened sending variables to python')
  end

  assert(length(ind) == 2)
  A = extractblock(out(ind(2):end));

  if (first_time)
    first_time = false;
  end
end
