#############################################################################
##
#W  carat.gi               Interface to Carat                   Franz G"ahler
##
#Y  Copyright (C) 1999-2012  Franz G"ahler,  Mathematik, Bielefeld University
##
##  Input/Output routines for interfacing with Carat data files
##


#############################################################################
##
#F  CaratTmpFile( name ) . . . . . returns file 'name' in Carat tmp directory
##
InstallGlobalFunction( CaratTmpFile, function( name )
    # if Carat temporary directory has disappeared, recreate it
    if not IsDirectoryPath( CARAT_TMP_DIR![1] ) then
        MakeReadWriteGlobal( "CARAT_TMP_DIR" );
        UnbindGlobal( "CARAT_TMP_DIR" );
        BindGlobal( "CARAT_TMP_DIR", DirectoryTemporary() );
    fi;
    return Filename( CARAT_TMP_DIR, name );
end );


#############################################################################
##
#F  CaratReadLine( input )  . . . . . . . . . . read line and discard comment
##
CaratReadLine := function( input )
    local str, pos;
    str := ReadLine( input );
    if str = fail then
        return fail;
    fi;
    pos := Position( str, '%' );
    if pos <> fail then
        return str{[1..pos-1]};
    else
        return str;
    fi;
end;


#############################################################################
##
#F  CaratNextNumber( str, start )  next number in string after position start
##

# this is a hack needed in CaratNextNumber, CaratReadNumbers
CaratReadPosition := 1;

CaratNextNumber := function( str, start )

    local len, pos1, pos2;

    len  := Length( str );
    pos1 := start;
    while pos1 <= len and not IsDigitChar( str[pos1] ) do
        pos1 := pos1 + 1;
    od;
    if pos1 > len then
        return fail;
    elif pos1 > 1 and str[pos1-1] = '-' then
        pos1 := pos1 - 1;
    fi;

    pos2 := pos1 + 1;
    while pos2 <= len and IsDigitChar( str[pos2] ) do
        pos2 := pos2 + 1;
    od;
    CaratReadPosition := pos2;

    return Int( str{[pos1..pos2-1]} );

end;
 

#############################################################################
##
#F  CaratReadNumbers( input, ntot )  . . . . . . read ntot numbers from input
##
CaratReadNumbers := function( input, ntot )

    local res, nread, str, n;

    res := [];
    nread := 0;

    while nread < ntot do

        str := ReadLine( input );
        n := CaratNextNumber( str, 1 );
        while n <> fail do
            Add( res, n );
            nread := nread + 1;
            n := CaratNextNumber( str, CaratReadPosition );
        od;

    od;
    return res;

end; 


#############################################################################
##
#F  CaratReadMatrixScalar( input, dim ) . . . . read scalar matrix from input
##
CaratReadMatrixScalar := function( input, dim )
    local lst;
    lst := CaratReadNumbers( input, 1 );
    return lst[1] * IdentityMat( dim );
end;


#############################################################################
##
#F  CaratReadMatrixDiagonal( input, dim ) . . read diagonal matrix from input
##
CaratReadMatrixDiagonal := function( input, dim )
    local lst, mat, i;
    lst := CaratReadNumbers( input, dim );
    mat := List( [1..dim], i -> List( [1..dim], j -> 0 ) );
    for i in [1..dim] do 
        mat[i][i] := lst[i];
    od;
    return mat;
end;


#############################################################################
##
#F  CaratReadMatrixSymmetric( input, dim ) . read symmetric matrix from input
##
CaratReadMatrixSymmetric := function( input, dim )
    local lst, mat, pos, i, j;
    lst := CaratReadNumbers( input, dim*(dim+1)/2 );
    mat := [];
    pos := 1;
    for i in [1..dim] do
        Add( mat, lst{[pos..pos+i-1]} );
        pos := pos + i;
    od;
    for i in [1..dim-1] do
        for j in [i+1..dim] do
            mat[i][j] := mat[j][i];
        od;
    od;
    return mat;
end;


#############################################################################
##
#F  CaratReadMatrixFull( input, n, m ) . . . . . . read nxm matrix from input
##
CaratReadMatrixFull := function( input, n, m )
    local lst;
    lst := CaratReadNumbers( input, n*m );
    return List( [1..n], i -> lst{[ (i-1)*m+1 .. i*m ]});
end;


#############################################################################
##
#F  CaratReadMatrix( input, str )  . . read matrix with header str from input
##
CaratReadMatrix := function( input, str )

    local n, den, pos, m;

    n   := CaratNextNumber( str, 1 );

    # is matrix rational?
    pos := Position( str, '/' );
    if pos <> fail then
        den := CaratNextNumber( str, pos + 1 );
    else
        den := 1;
    fi;

    # matrix format with 'x'
    pos := Position( str, 'x' );
    if pos <> fail then
        m := CaratNextNumber( str, pos + 1 );
        if m > 0 then
            return CaratReadMatrixFull( input, n, m ) / den;
        else
            return CaratReadMatrixSymmetric( input, n ) / den;
        fi;
    fi;

    # matrix format with 'd'
    pos := Position( str, 'd' );
    if pos <> fail then
        m := CaratNextNumber( str, pos + 1 );
        if m = 1 then
            return CaratReadMatrixDiagonal( input, n ) / den;
        else
            return CaratReadMatrixScalar( input, n ) / den;
        fi;

    fi;

    # full square matrix
    return CaratReadMatrixFull( input, n, n ) / den;

end;


#############################################################################
##
#F  CaratReadMatrices( input, n ) . .. . . . . . . read n matrices from input
##
CaratReadMatrices := function( input, n )

    local res, i, str;

    res := [];
    for i in [1..n] do
        str := CaratReadLine( input );
        Add( res, CaratReadMatrix( input, str ) );
    od;
    return res;

end;        


#############################################################################
##
#F  CaratReadMatrixFile( filename )  . . . . . . . . . read Carat matrix file
##
InstallGlobalFunction( CaratReadMatrixFile, function( filename )

    local input, str, pos, n, res, i;

    input := InputTextFile( filename );
    str   := CaratReadLine( input );
    pos   := Position( str, '#' );

    # several matrices
    if pos <> fail then
        n   := CaratNextNumber( str, pos + 1 );
        res := CaratReadMatrices( input, n );

    # just one matrix
    else
        res := CaratReadMatrix( input, str );
    fi;

    CloseStream( input );
    return res;

end ); 


#############################################################################
##
##  A bravais record is a record with components
##
##  generators:   generators of the group
##  formspace:    basis of space of invariant forms (optional)
##  centerings:   basis of centering matrices (optional)
##  normalizer:   additional generators of normalizer in GL(n,Z) (optional)
##  centralizer:  additional generators of centralizer in GL(n,Z) (optional)
##  size:         size of the group
##


#############################################################################
##
#F  CaratReadBravaisRecord( input, str )  . . . . . read Carat Bravais record
##
CaratReadBravaisRecord := function( input, str )

    local res, pos, n, line;

    res := rec();

    # read group generators
    pos := Position( str, 'g' );

    # normal Bravais file format
    if pos <> fail then
        n := CaratNextNumber( str, pos + 1 );
        res.generators := CaratReadMatrices( input, n );

    # just generators, matrix file format
    # could the size also be given?
    else
        n := CaratNextNumber( str, 2 );
        res.generators := CaratReadMatrices( input, n );
        return res;
    fi;

    # read form space basis
    pos := Position( str, 'f' );
    if pos <> fail then
        n := CaratNextNumber( str, pos + 1 );
        res.formspace := CaratReadMatrices( input, n );
    fi;

    # read centering matrices
    pos := Position( str, 'z' );
    if pos <> fail then
        n := CaratNextNumber( str, pos + 1 );
        res.centerings := CaratReadMatrices( input, n );
    fi;

    # read normalizer generators
    pos := Position( str, 'n' );
    if pos <> fail then
        n := CaratNextNumber( str, pos + 1 );
        res.normalizer := CaratReadMatrices( input, n );
    fi;

    # read centralizer generators
    pos := Position( str, 'c' );
    if pos <> fail then
        n := CaratNextNumber( str, pos + 1 );
        res.centralizer := CaratReadMatrices( input, n );
    fi;

    # read size of the group; is optional, but not announced in the header
    line := ReadLine( input );
    if line <> fail then
        pos := Position( line, '=' );
        res.size := CaratNextNumber( line, pos + 1 );
    fi;

    return res;

end;


#############################################################################
##
#F  CaratReadBravaisFile( filename )  . . . . . . . . read Carat Bravais file
##
InstallGlobalFunction( CaratReadBravaisFile, function( filename )

    local input, str, res;

    input := InputTextFile( filename );
    str   := CaratReadLine( input );
    res   := CaratReadBravaisRecord( input, str );

    CloseStream( input );
    return res;

end ); 


#############################################################################
##
#F  CaratReadMultiBravaisFile( filename ) . . . read Carat Multi-Bravais file
##
InstallGlobalFunction( CaratReadMultiBravaisFile, function( filename )

    local input, str, res;

    input := InputTextFile( filename );
    str   := CaratReadLine( input );
    res   := rec( info := [], groups := [] );

    # read comments until first Bravais record starts
    while Length( str ) < 2 or str{[1..2]} <> "#g" do
        Add( res.info, str );
        str := CaratReadLine( input );
    od;

    # read the Bravais records
    while str <> fail and Length( str ) >= 2 and str{[1..2]} = "#g" do
        Add( res.groups, CaratReadBravaisRecord( input, str ) );
        str := CaratReadLine( input );
    od;

    CloseStream( input );
    return res;

end ); 


#############################################################################
##
#F  CaratWriteMatrix( output, mat )  . . . . . . write Carat matrix to stream
##
CaratWriteMatrix := function( output, mat )

    local d, str, i, j;

    # for simplicity, we always write in full format
    d := DimensionsMat( mat );
    if d[1]=d[2] then
        str := String( d[1] );
    else
        str := Concatenation( String( d[1] ), "x", String( d[2] ) );
    fi;

    WriteLine( output, str );
    for i in [1..d[1]] do
        str := "";
        Append( str, String( mat[i][1] ) );
        for j in [2..d[2]] do
            Append( str, " " );
            Append( str, String( mat[i][j] ) );
        od;
        WriteLine( output, str );
    od;

end;


#############################################################################
##
#F  CaratWriteMatrixFile( filename, data )  . write Carat matrix data to file
##
InstallGlobalFunction( CaratWriteMatrixFile, function( filename, data )

    local output, header, mat;

    output := OutputTextFile( filename, false );

    # one matrix
    if IsMatrix( data ) then
        CaratWriteMatrix( output, data );

    # list of matrices
    else
        header := Concatenation( "#", String( Length( data ) ) );
        WriteLine( output, header );
        for mat in data do
            CaratWriteMatrix( output, mat );
        od;
    fi;
    CloseStream( output );

end );


#############################################################################
##
#F  CaratFactorString( n )  . . . . factorization of n in Carat string format
##
CaratFactorString := function( n )

    local fac, set, ExpStr, str, p;

    fac := FactorsInt( n );
    set := Set( fac );

    ExpStr := function( n )
        return String( Number( fac, x -> x = n ) );
    end;

    str := Concatenation( String( set[1] ), "^", ExpStr( set[1] ) ); 

    for p in set{[2..Length(set)]} do
         str := Concatenation( str, " * ", String( p ), "^", ExpStr( p ) );
    od;

    str := Concatenation( str, " = ", String( n ) );

    return str;

end;


#############################################################################
##
#F  CaratWriteBravaisFile( filename, data )  write Carat Bravais rec. to file
##
InstallGlobalFunction( CaratWriteBravaisFile, function( filename, data )

    local output, header, len, i;

    # first construct the header with its different entries

    header := "#";

    if IsBound( data.generators ) then
        len    := Length( data.generators );
        header := Concatenation( header, "g", String( len ), " " );
    fi;

    if IsBound( data.formspace ) then
        len    := Length( data.formspace );
        header := Concatenation( header, "f", String( len ), " " );
    fi;

    if IsBound( data.centerings ) then
        len    := Length( data.centerings );
        header := Concatenation( header, "z", String( len ), " " );
    fi;

    if IsBound( data.normalizer ) then
        len    := Length( data.normalizer );
        header := Concatenation( header, "n", String( len ), " " );
    fi;

    if IsBound( data.centralizer ) then
        len    := Length( data.centralizer );
        header := Concatenation( header, "c", String( len ), " " );
    fi;

    # open file and write the header
    output := OutputTextFile( filename, false );
    WriteLine( output, header );

    # write all componentes of the data

    if IsBound( data.generators ) then
        for i in [1..Length( data.generators )] do
            CaratWriteMatrix( output, data.generators[i] );
        od;
    fi;

    if IsBound( data.formspace ) then
        for i in [1..Length( data.formspace )] do
            CaratWriteMatrix( output, data.formspace[i] );
        od;
    fi;

    if IsBound( data.centerings ) then
        for i in [1..Length( data.centerings )] do
            CaratWriteMatrix( output, data.centerings[i] );
        od;
    fi;

    if IsBound( data.normalizer ) then
        for i in [1..Length( data.normalizer )] do
            CaratWriteMatrix( output, data.normalizer[i] );
        od;
    fi;

    if IsBound( data.centralizer ) then
        for i in [1..Length( data.centralizer )] do
            CaratWriteMatrix( output, data.centralizer[i] );
        od;
    fi;

    if IsBound( data.size ) then
        WriteLine( output, CaratFactorString( data.size ) );
    fi;

    CloseStream( output );

end );


#############################################################################
##
##  General purpose commands for working with Carat
##


#############################################################################
##
#F  CaratShowFile( filename )  . . . . . . . . . display contents of filename
##
InstallGlobalFunction( CaratShowFile, function( filename )
    local input;
    input := InputTextFile( filename );
    if input = fail then
        Error( Concatenation( "File ", filename, " not found." ) );
    fi;
    Print( ReadAll( input ) );
    CloseStream( input );
    return;
end );


#############################################################################
##
#F  CaratStringToWordList( string )  . . . . . .cut string into list of words
##
CaratStringToWordList := function( string )

    local seps, lst, str, pos1, pos2;

    seps := [ ' ', '\t', '\n' ];
    lst  := [];
    str  := string;
    pos1 := Position( str, First( str, x -> not x in seps ) );
    while pos1 <> fail do
        str := str{[pos1..Length(str)]};
        pos2 := Position( str, First( str, x -> x in seps ) );
        if pos2 = fail then
            pos2 := Length( str ) + 1;
        fi;
        Add( lst, str{[1..pos2-1]} );
        str := str{[pos2..Length(str)]};
        pos1 := Position( str, First( str, x -> not x in seps ) );
    od;
    return lst;

end;


#############################################################################
##
#F  CaratCommand( cmd, args, outfile )  exec cmd with args, output to outfile
##
InstallGlobalFunction( CaratCommand, function( command, args, outfile )

    local program, output, err;

    # find executable
    program := Filename( CARAT_BIN_DIR, command );    
    if program = fail then
        Error( Concatenation( "Carat program ", command, " not found." ) );
    fi;

    # execute command
    output := OutputTextFile( outfile, false );
    err    := Process( DirectoryCurrent(), program, InputTextNone(), 
                       output, CaratStringToWordList( args ) );
    CloseStream( output );

    # did it work?
    if err = 2 and args <> "-h" then   # we used wrong arguments
        CaratShowFile( outfile );      # contains usage advice
    fi;
    if err < 0 and args <> "-h" then
        Error( Concatenation( "Carat program ", command,
                              " failed with error code ", String(err) ) );
    fi;

end );


#############################################################################
##
#F  CaratHelp( command )  . . . . . . . display online help for Carat command
##
InstallGlobalFunction( CaratHelp, function( command )
    CaratCommand( command, "-h", "*stdout*" );
end );




