##############################################################################
# functions that read from the stream, low level




IF_READ_NON_BLOCKING := function ( stream )
    local  sl, outl;
    outl := "";
    repeat
        sl := READ_IOSTREAM_NOWAIT( stream![1], 1 );
        if sl <> fail  then
            Append( outl, sl );
        fi;
    until sl = fail;
    return outl;
end;




IF_READ_UNTIL_PROMPT := function ( cas )
    local  OUT, out, prompt_arrived;

    prompt_arrived := function ( cas, OUT )
          if IsFunction( IF_PROMPTS[cas] )  then
              return IF_PROMPTS[cas]( OUT );
          else
              return ForAny( IF_PROMPTS[cas],  x -> PositionSublist( OUT, x ) <> fail );
          fi;
      end;

    OUT := "";
    repeat
#        out := ReadAll( IF_IOLP[cas] );
        out := IF_READ_NON_BLOCKING( IF_IOLP[cas] );
        Append( OUT, out );
    until prompt_arrived( cas, OUT );
    Info( InfoInterfaces, 3, "IF output: ", OUT );
    return IF_FUNC_REMOVE_PROMPT[cas]( OUT );
end;




##############################################################################
# functions that communicate with the stream, low level




IF_WriteAndRead := function ( cas, string )
    local  res;
    Info( InfoInterfaces, 3, "IF input: ", string );
    WriteLine( IF_IOLP[cas], string );
    if ValueOption( "IF_Get_OpenMath" ) = true  then
        Info( InfoInterfaces, 2, "IF output: <OpenMath>" );
        res := OMGetObject( IF_IOLP[cas] );
        IF_READ_UNTIL_PROMPT( cas );
    else
        res := IF_READ_UNTIL_PROMPT( cas );
    fi;
    return res;
end;




IF_WriteAndRead_Split_NoPrompt := function ( cas, input )
    local  i, res;
    Info( InfoInterfaces, 3, "IF input: ", input );
    input := SplitString( input, '\n' );
    res := "";
    for i  in [ 1 .. Length( input ) ]  do
        WriteLine( IF_IOLP[cas], input[i] );
        if IF_IS_THERE_PROMPT[cas]  then
            res := IF_READ_UNTIL_PROMPT( cas );
        fi;
    od;
    return res;
end;




##############################################################################
# functions that handle the interfaced CAS




# this function checks the executable file, and if needed tries to
# autodetermine, or prints an appropriate error message

IF_CHECK_EXECUTABLE := function ( cas )
    local  IsExec, to_try, exec, exec_full_path, exec_name;

    IsExec := path -> IsString( path ) and IsDirectoryPath( path ) <> true and IsExecutableFile( path ) = true;

    if IsBound( IF_EXEC_FULL_PATH[cas] ) and IsExec( IF_EXEC_FULL_PATH[cas] )  then
        return IF_EXEC_FULL_PATH[cas];
    fi;

    while true do

        if IsBound( IF_EXEC_FULL_PATH[cas] ) and IsString( IF_EXEC_FULL_PATH[cas] )  then
            exec_full_path := IF_EXEC_FULL_PATH[cas];
        else 
            exec_full_path := "/";
        fi;

        if IsBound( IF_EXEC_NAME[cas] ) and IsString( IF_EXEC_NAME[cas] )  then
            exec_name := IF_EXEC_NAME[cas];
        else 
            exec_name := "";
        fi;

        to_try := [ exec_full_path, exec_name, Filename( Directory( exec_full_path ), exec_name ),
          Filename( DirectoriesSystemPrograms(  ), exec_name ), Filename( DirectoriesSystemPrograms(  ), exec_full_path ),
          Filename( DirectoryCurrent(  ), exec_name ), Filename( DirectoryCurrent(  ), exec_full_path ) ];

        for exec  in to_try  do
            if IsExec( exec )  then
                break;
            fi;
        od;

        if IsExec( exec )  then
            break;
        fi;

        Print( "  Type 'IF_EXEC_FULL_PATH[<IF_xxx>]:=\"<path>\"; return;' where\n" );
        Print( "  <path> is the path of the interfaced CAS executable file on your system,\n" );
        Print( "  and <IF_xxx> is the conventional name of the interfaced CAS.\n" );

        if not IsBound( IF_EXEC_FULL_PATH[cas] )  then
            Print( "  Now 'IF_EXEC_FULL_PATH[<IF_xxx>]' is not bound.\n" );
        elif not IsString( IF_EXEC_FULL_PATH[cas] )  then
            Print( "  Now 'IF_EXEC_FULL_PATH[<IF_xxx>]' must be a string.\n" );
	elif not IsExistingFile( IF_EXEC_FULL_PATH[cas] )  then
	    Print( "  Now '", IF_EXEC_FULL_PATH[cas], "' does not exist.\n" );
        elif IsDirectoryPath( IF_EXEC_FULL_PATH[cas] ) = true  then
            Print( "  Now 'IF_EXEC_FULL_PATH[<IF_xxx>]' must be a file, not a directory.\n" );
        elif IsExecutableFile( IF_EXEC_FULL_PATH[cas] ) <> true  then
            Print( "  Now '", IF_EXEC_FULL_PATH[cas], "' is not an executable file.\n" );
        fi;

        Error( "Executable not found!\n" );

    od;

    Info( InfoInterfaces, 2, "found executable ", exec );
    return exec;

end;




# A function for closing the interface

IF_CloseInterface := function ( cas )
    if IsBound( IF_IOLP[cas] ) and IsStream( IF_IOLP[cas] )  then
        if not IsClosedStream( IF_IOLP[cas] )  then
            WriteLine( IF_IOLP[cas], IF_QUIT_COMMAND[cas] );
            Sleep( 1 );
            CloseStream( IF_IOLP[cas] );
        else
            Info( InfoInterfaces, 2, "Interface already closed." );
        fi;
    fi;
end;




# A function for killing the interface, copy of the previous function

IF_KillInterface := function ( cas )
    if IsBound( IF_IOLP[cas] ) and IsStream( IF_IOLP[cas] )  then
        if not IsClosedStream( IF_IOLP[cas] )  then
#            WriteLine( IF_IOLP[cas], IF_QUIT_COMMAND[cas] );
#            Sleep( 1 );
            CloseStream( IF_IOLP[cas] );
        else
            Info( InfoInterfaces, 2, "Interface already closed." );
        fi;
    fi;
end;




IF_StartInterface := function ( cas )
    local  out, OUT, exec;

    IF_CloseInterface( cas );

    if IsBound( IF_PROMPT_NR[cas] )  then
        IF_PROMPT_NR[cas]:= 1;
    fi;

    exec := IF_CHECK_EXECUTABLE( cas );

    IF_IOLP[cas] := InputOutputLocalProcess( IF_TEMP_DIRECTORY, exec, IF_EXEC_OPTIONS[cas] );
    InstallAtExit( function (  ) IF_CloseInterface( cas ); end );

#    Sleep(...);

    OUT := "";
    repeat
#        out := ReadAll( IF_IOLP[cas] );
        out := IF_READ_NON_BLOCKING( IF_IOLP[cas] );
        Append( OUT, out );
    until PositionSublist( OUT, IF_FIRST_PROMPT[cas] ) <> fail;
    Info( InfoInterfaces, 3, "IF output: ", OUT );


    if IsBound( IF_FIRST_COMMAND[cas] ) then
        IF_WriteAndRead_Split_NoPrompt( cas, IF_FIRST_COMMAND[cas] );
    fi;

end;




##############################################################################
# functions that handle the conversion of objects




IF_Type := function ( cas, obj )
    local  i;
    for i  in RecNames( IF_DATA_TYPES[cas] )  do
        if IsBound( IF_DATA_TYPES[cas].(i)[2] ) and IF_DATA_TYPES[cas].(i)[2]( obj )  then
            return i;
        fi;
    od;
    return fail;
end;




IF_ConvertFromGap := function ( cas, obj )
    local  type, i;
    type := IF_Type( cas, obj );

    if type in RecNames( IF_DATA_TYPES[cas] ) and IsBound( IF_DATA_TYPES[cas].(type)[3])  then
        return IF_DATA_TYPES[cas].(type)[3]( obj );
    fi;

    return String( obj );
    return fail;
end;




_EVALSTRINGTMP := 0;
IF_EvalString := function ( s )
    local  a, f, res;
    a := "_EVALSTRINGTMP:=";
    Append( a, s );
    Add( a, ';' );
    Unbind( _EVALSTRINGTMP );
    f := InputTextString( a );
    Read( f );
    if not IsBound( _EVALSTRINGTMP )  then
        res := s;
    else
        res := _EVALSTRINGTMP;
    fi;
    Unbind( _EVALSTRINGTMP );
    return res;
end;
Unbind( _EVALSTRINGTMP );




IF_ConvertToGap := function ( cas, obj, type, name )
    local  i;

    if type in RecNames( IF_DATA_TYPES[cas] ) and IsBound( IF_DATA_TYPES[cas].(type)[4] )  then
        if NumberArgumentsFunction( IF_DATA_TYPES[cas].(type)[4] ) = 2 then 
            return IF_DATA_TYPES[cas].(type)[4]( obj, name );
        else
            return IF_DATA_TYPES[cas].(type)[4]( obj );
        fi;
    fi;

    return IF_EvalString( obj );
    return obj;
end;




##############################################################################
# functions that manage interface




# This function does the same as IF_Interface, but using only strings,
# and without any conversion. Usually <destination> is the empty string
# "". If <destination> is a non-empty string, then the output is
# assigned, in the interfaced CAS, to the variable <destination>, and no
# value is returned in Gap.

IF_LowLevelInterface := function ( cas, destination, command, arguments )
    local  length, i, input, brackets;

    # calculate the input
    if IsBound ( IF_BRACKETS[cas] ) then
        brackets := IF_BRACKETS[cas];
    else
        brackets := [ "(", ")" ];
    fi;

    if destination = ""  then
        input := "";
    elif IsBound( IF_ASSIGN_PREFIX[cas] )  then
        input := Concatenation( IF_ASSIGN_PREFIX[cas], " ", destination, " ", IF_ASSIGN[cas], " " );
    else
        input := Concatenation( destination, " ", IF_ASSIGN[cas], " " );
    fi;

    input := Concatenation( input, command, brackets[1], " " );
    if IsString( arguments ) and IsStringRep( arguments )  then
        Append( input, arguments );
    else
        length := Length( arguments );
        for i  in [ 1 .. length ]  do
            Append( input, arguments[i] );
            if i < length  then
                Append( input, ", " );
            fi;
        od;
    fi;
    Append( input, " " );
    Append( input, brackets[2] );

    if destination = ""  then
        Append( input, IF_END_COMMAND[cas] );
    else
        Append( input, IF_END_COMMAND_NO_OUTPUT[cas] );
    fi;


    # send the input and in case return the output
    if destination = ""  then
        return IF_WriteAndRead( cas, input );
    else
        IF_WriteAndRead_Split_NoPrompt( cas, input );
    fi;


end;




IF_GetCasOutput := function( cas, name )

    local type, input, out;

    # if OpenMath output is available, then use it directly
    if IsBound ( IF_FUNC_DISPLAY_OPENMATH[cas] ) then
        return IF_LowLevelInterface( cas, "", IF_FUNC_DISPLAY_OPENMATH[cas], name : IF_Get_OpenMath );
    fi;

    # get the output type
    if IsFunction( IF_FUNC_TYPE[cas] ) then 
        type := IF_FUNC_TYPE[cas]( name );
    else
        type := IF_LowLevelInterface( cas, "", IF_FUNC_TYPE[cas], name );
        if IF_TYPE_IS_QUOTED[cas]  then
            type := IF_EvalString( type );
        fi;
    fi;
    Info( InfoInterfaces, 2, "IF type: ", type );

    # get the output
    out := IF_LowLevelInterface( cas, "", IF_FUNC_DISPLAY[cas] , name );

    return IF_ConvertToGap( cas, out, type, name );
end;



  IF_maple := "2b defined";
  IF_mupad := "2b defined";
  IF_pari_gp := "2b defined";
  IF_mathematica := "2b defined";
  IF_ghci := "2b defined";


IF_Interface := function ( cas, command, arguments )

    local GapObj;
    if cas = IF_ghci  then
        GapObj := "gapobj";
    else
        GapObj := "GapObj";
    fi;

    # start the interface if needed
    if not IsBound( IF_IOLP[cas] ) or not IsStream( IF_IOLP[cas] ) or IsClosedStream( IF_IOLP[cas] )  then
        IF_StartInterface(cas);
    fi;

    if not (IsString( arguments ) and IsStringRep( arguments ))  then
        arguments := List( arguments, x -> IF_ConvertFromGap( cas, x ) );
    fi;

    # send the input and return the output
    IF_LowLevelInterface( cas, GapObj, command, arguments );

    return IF_GetCasOutput( cas, GapObj );

end;





IF_Maple_op := function ( cas, obj, name, oper )

    local  length, i, res, subname, subres, string, op, nops, order;

    # Maple types handled by this function:
    # `*`, `+`, `.`, `..`, `^`, fraction, complex,
    # list, set, exprseq;

    # MuPAD types handled by this function:
    # DOM_COMPLEX, DOM_LIST, DOM_SET;

    # PARI/GP types handled by this function:
    # t_INTMOD, t_FRAC, t_FRACN, t_COMPLEX, t_PADIC, t_RFRAC, t_RFRACN, t_VEC, t_LIST;

    Info( InfoInterfaces, 2, "IF_Maple_op, arg = ", obj, ", ", name, ", ", oper );

    if cas = IF_maple  then
        op := "op";
        nops := "nops";
        order := false;
    elif cas = IF_mupad  then
        op := "op";
        nops := "nops";
        order := true;
    elif cas = IF_pari_gp  then
        op := "component";
        nops := "length";
        order := true;
    elif cas = IF_mathematica  then
        op := "Part";
        nops := "Length";
        order := true;
    fi;

    length := Int( IF_LowLevelInterface( cas, "", nops, name ) );

    if oper = Add  then
        res := [  ];
    fi;

    for i  in [ 1 .. length ]  do

        subname := Concatenation( name, "s", String( i ) );

        if order  then
            IF_LowLevelInterface( cas, subname, op, [ name, String( i ) ] );
        else
            IF_LowLevelInterface( cas, subname, op, [ String( i ), name ] );
        fi;

        subres := IF_GetCasOutput( cas, subname );

        if oper = Add  then
            oper( res, subres );
        elif i = 1  then
            res := subres;
        elif oper = \^ and subres = 1/2  then
            return Sqrt( res );
        else
            res := oper( res, subres );
        fi;
    od;

    Info( InfoInterfaces, 2, "IF_Maple_op, res = ", res );


    return res;

end;

