#!/bin/bash
#===============================================================================
#
# cg_d.sh - generates source code for d-slash
#
#-------------------------------------------------------------------------------
#
# Copyright (C) 2015-2017 Hinnerk Stueben
#
# This file is part of BQCD -- Berlin Quantum ChromoDynamics program
#
# BQCD 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.
#
# BQCD 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 BQCD.  If not, see <http://www.gnu.org/licenses/>.
#
#------------------------------------------------------------------------------

set -u
#------------------------------------------------------------------------------
function Usage
{
cat << 'EOF' 1>&2
Usage: d.sh [--print-args] [--print-filename] [void] \
            mu={x|y|z|t} | --from-filename=<filename> [prefix=<prefix>] \
            [fwd_bwd]={fwd|bwd} \
            [dagger] [first] [block] [rkind={4|8}] [simd] \
            [version=<version>] [timing_bin=<timing_bin>] \
            [sctype=<sctype>] [lang={C|Fortran}] [undersore]
EOF
exit 1
}

#-------------------------------------------------------------------------------
function Die
{
    echo "d.sh: $@" 1>&2
    exit 1
}

#------------------------------------------------------------------------------
function ToLower
{
    echo "$@" | tr '[:upper:]' '[:lower:]'
} 

#------------------------------------------------------------------------------
function ToUpper
{
    echo "$@" | tr '[:lower:]' '[:upper:]'
} 

#-------------------------------------------------------------------------------
function PrintVar
{
    typeset value
    typeset fmt='%12s = %s\n'

    eval "value=\"\$$1\""

    case $value in
        '')   printf "$fmt" $1 '<empty>' 1>&2 ;;
        ' ')  printf "$fmt" $1 '<space>' 1>&2 ;;
        *)    printf "$fmt" $1 "$value" 1>&2 ;;
    esac
}

#------------------------------------------------------------------------------
function PrintArgs
{
    typeset Name=$(PrintName)
    typeset Filename=$(PrintFilename)

    PrintVar version
    PrintVar sctype
    PrintVar mu
    PrintVar dagger
    PrintVar first
    PrintVar fwd_bwd
    PrintVar block
    PrintVar rkind
    PrintVar simd
    PrintVar simdflag
    PrintVar simdkind
    PrintVar underscore    
    PrintVar lang
    PrintVar prefix
    PrintVar Filename
    PrintVar Name
    PrintVar timing_bin
}

#------------------------------------------------------------------------------
function PrintName
{
    Name
}

#------------------------------------------------------------------------------
function PrintFilename
{
    echo $prefix$(Name)
}

#------------------------------------------------------------------------------
function Name  # code parameters into a name
{
    #if ((from_filename)); then
    #    echo ${filename%.*}
    #    return
    #fi

    if ((dagger)); then
        nameDAGGER=_dag_
    else
        nameDAGGER=_
    fi

    nameXYZT=$mu
    nameTYPE=$sctype

    if ((first)); then
        nameFIRST=1
    else
        nameFIRST=''
    fi

    if [[ $version = proj ]]; then
        case $fwd_bwd in
            fwd) nameFB=f ;;
            bwd) nameFB=b ;;
            *) Die "'fwd_bwd' unknown for 'proj'" ;;
        esac
    else
        nameFB=''
    fi

    if ((block)); then
        nameBLOCK=_block_
    else
        nameBLOCK=_
    fi

    if ((rkind == 4)); then 
        namePRECISION=_r4
    else
        namePRECISION=_r8
    fi

    if ((simd)); then
        nameSIMD=s
    else
        nameSIMD=''
    fi

  ## underscore is now treated via "#define NamesToLower_"
  ##echo "cg_${version}${nameDAGGER}${nameXYZT}${nameFIRST}${nameBLOCK}${nameTYPE}${namePRECISION}${nameSIMD}${underscore}"
    echo "cg_${version}${nameDAGGER}${nameXYZT}${nameFIRST}${nameFB}${nameBLOCK}${nameTYPE}${namePRECISION}${nameSIMD}" | sed s/__/_/g
}

#------------------------------------------------------------------------------
function SetTimingBin
{
    case $version in
        d*)   timing_bin="cg_d_${mu}";;
        proj) timing_bin="cg_proj_${mu}";;
        projection) timing_bin="cg_sc2_projection";;
    esac
}

#------------------------------------------------------------------------------
function FromFilename  # get parameters from filename (inverse of Name)
{
    typeset ifs=$IFS
    typeset -i cg=0

    case $filename in
        *.c)   lang=C;;
        *.i)   lang=C;;
        *.F90) lang=Fortran;;
        *.f90) lang=Fortran;;
        *)     Die "$filename: unknown filename extension";;
    esac

    case $filename in
        *_.*) underscore=_;;
        *)    underscore=;;
    esac

    IFS=_
    set -- ${filename%.*}

    case $1 in
        *cg) 
            cg=1
            prefix=${1%cg}
            shift
            ;;
    esac

    version=$1; shift
    
    case $1 in
        dag) dagger=1; shift ;;
        *)   dagger=0;
    esac

    case $1 in
        [xyzt])  mu=$1; first=0; shift ;;
        [xyzt]1) mu=${1%1}; first=1; shift ;;
        [xyzt]f) mu=${1%f}; fwd_bwd=fwd; shift ;;
        [xyzt]b) mu=${1%b}; fwd_bwd=bwd; shift ;;
    esac

    case $1 in
        block) block=1; shift ;;
        *)     block=0
    esac

    if ((cg)); then
       sctype=$1
       shift
    fi

    case $1 in
        r4)  rkind=4; simd=0 ;;
        r8)  rkind=8; simd=0 ;;
        r4s) rkind=4; simd=1 ;;
        r8s) rkind=8; simd=1 ;;
    esac

    if ((cg)); then
        SetTimingBin
    else
        case $mu in
            x|y|z) timing_bin="d_${mu}f" ;;
            t)     timing_bin="d_${mu}" ;;
        esac
    fi

    IFS=$ifs
}

#------------------------------------------------------------------------------
function PrintOldOptions
{
    if ((dagger)); then
        echo -DDAGGER
    else
        echo -UDAGGER
    fi

    eval echo "-DDIR_\$(ToUpper $mu)"

    if ((first)); then
        echo -DFIRST
    else
        echo -UFIRST
    fi   

    if ((block)); then
        echo -DBLOCK
    else
        echo -UBLOCK
    fi

    echo -DSPINCOL_TYPE=$sctype 

    case $rkind in
        4) echo -DPRECISION_R4;;
        8) echo -DPRECISION_R8;;
    esac

    if ((simd)); then
        echo -DSIMD
    else
        echo -USIMD
    fi
}

#------------------------------------------------------------------------------
function PrintScKind
{
    case $sctype in
        sc)   echo 1 ;;
        sc22) echo 22 ;;
    esac
}

#==============================================================================
# command line arguments

prefix=
version=d2
sctype=sc
filename=
timing_bin=
mu=
fwd_bwd=fwd
lang=Fortran
underscore=
simdflag=
simdkind=
typeset -i dagger=0
typeset -i first=0
typeset -i block=0
typeset -i rkind=8
typeset -i simd=0

typeset -i print_args=0
typeset -i print_filename=0
typeset -i from_filename=0

if (($# == 0)); then Usage; fi

while (($# > 0))
do
    case $1 in
        --print-old-options-from=*)
                      filename=${1#--print-old-options-from=}
                      FromFilename
                      PrintOldOptions
                      exit
                      ;;
        --print-args) print_args=1;;
        --print-filename) print_filename=1;;
        --from-filename=*)  
                      from_filename=1
                      filename=${1#--from-filename=}
                      ;;
        prefix=*)     prefix=${1#prefix=};;
        version=d2)   version=d2;;
        version=d21)  version=d21;;
        version=d102) version=d102;;
        version=proj) version=proj;;  # projection to two component spinors
        sctype=sc)    sctype=sc;;
        sctype=sc22)  sctype=sc22;;
        timing_bin=*) timing_bin=${1#timing_bin=};;
        mu=x)         mu=x;;
        mu=y)         mu=y;;
        mu=z)         mu=z;;
        mu=t)         mu=t;;
        fwd_bwd=fwd)  fwd_bwd=fwd;;
        fwd_bwd=bwd)  fwd_bwd=bwd;;
        dagger)       dagger=1;;
        first)        first=1;;
        block)        block=1;;
        rkind=4)      rkind=4;;
        rkind=8)      rkind=8;;
        simd)         simd=1;;
        simdflag=*)   simdflag=${1#simdflag=};;
        lang=C)       lang=C;;
        lang=Fortran) lang=Fortran;;
        underscore)   underscore=_;;
        void)         : ;;  # placeholder in shell loops: 'for dagger in void dagger; do ...'
        *)            Usage;;
    esac
    shift
done

case $prefix in
    *_*) Die "$prefix: prefix must not contain an underscore" ;;
esac

if ((from_filename)); then 
    FromFilename
else
    if [[ -z $mu ]]; then Die 'mu={x|y|z|t} not set'; fi
    if [[ -z $timing_bin ]]; then SetTimingBin; fi
fi

if ((print_filename)); then 
    PrintFilename
else
    if ((simd)); then
        case $simdflag in
            *SSE*)      simdkind=intel;;
            *AVX*)      simdkind=intel;;
            *QPX*)      simdkind=ibm;;
            *GENERIC2*) simdkind=ibm;;
            *GENERIC*)  simdkind=intel;;
            ?*)         Die "$simdflag: unknown simdflag";;
            *)          Die "simdflag is missing";;
        esac
    fi
fi

if ((print_args)); then PrintArgs; fi

if ((print_args + print_filename)); then exit; fi

#------------------------------------------------------------------------------
# other variables

# both 'dagger' settings effectively change signs
if ((dagger)); then
    add=Sub
    sub=Add
    fwd=bwd
    bwd=fwd
else
    add=Add
    sub=Sub
    fwd=fwd
    bwd=bwd                
fi

# names of neighbour index variables
j_fwd=jf
j_bwd=jb

# language specific settings
case $lang in
    C)
        colons=''
        semicolon=';'
        continuation_mark='\'
        exclamation_mark=''
        int='int*'
        omp=''
        ;;
    Fortran)
        colons='::'
        semicolon=''
        continuation_mark='&'
        exclamation_mark='!'
        int=''
        omp='!$omp'
        ;;
esac

# types of temporary variables
if ((simd)); then
    real=SIMD_VECTOR
else
    real=REAL
fi

#if ((from_filename)); then
#    volh_tot=
#else
    volh_tot="$int volh_tot,"
#fi

if ((block)); then
    varsBLOCK="$volh_tot $int i1, $int i2"
else
    varsBLOCK="$volh_tot $int volh"
fi

#==============================================================================
# indentation:

indent=

case $lang in
    C)       nindent='    ';;
    Fortran) nindent='   ';;
esac

#------------------------------------------------------------------------------
function IndentPush
{
    indent="$indent$nindent"
}

#------------------------------------------------------------------------------
function IndentPop
{
    indent=${indent%$nindent}
}

#------------------------------------------------------------------------------
function Echo
{
    echo "$indent$@"
}

#==============================================================================
function Abs
{
    case $lang in
        C)       echo "($1) < 0 ? -($1) : ($1)";;
        Fortran) echo "abs($1)";;
    esac
}

#------------------------------------------------------------------------------
function Define
{
    echo "# define $@"
} 

#------------------------------------------------------------------------------
function Undef
{
    echo "# undef $1"
} 

#------------------------------------------------------------------------------
function Include
{
    echo "# include \"$1\""
} 

#------------------------------------------------------------------------------
function Ifdef
{
    echo "#ifdef $1"
} 

#------------------------------------------------------------------------------
function Ifndef
{
    echo "#ifndef $1"
} 

#------------------------------------------------------------------------------
function EndIfdef
{
    echo "#endif"
} 

#------------------------------------------------------------------------------
function Integer
{
    case $lang in
        C)       Echo "int $*;" ;;
        Fortran) Echo "integer :: $*" ;;
    esac
}

#------------------------------------------------------------------------------
function If
{
    case $lang in
        C)       Echo "if $@ {";;
        Fortran) Echo "if $@ then";;
    esac
    IndentPush
}

#------------------------------------------------------------------------------
function Else
{
    IndentPop
    case $lang in
        C)       Echo "} else {";;
        Fortran) Echo "else";;
    esac
    IndentPush
}

#------------------------------------------------------------------------------
function EndIf
{
    IndentPop
    case $lang in
        C)       echo "}";;
        Fortran) Echo "endif";;
    esac
}

#------------------------------------------------------------------------------
function Header
{
    if ((simd)); then
    if [[ $mu = x ]]; then
        Define SIMD_HACK
        Echo "$exclamation_mark//SIMD_HACK"
    fi
    fi

    if [[ $lang = C ]];       then Ifndef LANGUAGE_C; fi
    if [[ $lang = C ]];       then Define LANGUAGE_C; fi
    if [[ $lang = C ]];       then EndIfdef; fi
    if [[ $lang = Fortran ]]; then Define LANGUAGE_FORTRAN; fi
                                   Define SPINCOL_TYPE $sctype
                                   Define SC_KIND $(PrintScKind)
                                   Define NAME $(Name)
                                   Define TRACE_NAME $(Name)
                                   Ifdef NamesToLower_
                                   Undef NAME
                                   Define NAME $(Name)_
                                   EndIfdef
                                   Define varsBLOCK $varsBLOCK
                                   Define PRECISION_R$rkind
    if ((simd));              then Define SIMD; fi

    if [[ $lang = Fortran ]]; then Include defs.h; fi
    if [[ $lang = C ]];       then Include c_defs.h; fi
                                   Include simd.h
                                   Include alignment.h
                                   Include cg_defs.h
                                   Include cg_d.h
    echo
}    

#------------------------------------------------------------------------------
function Alignment
{
    Echo '#ifdef ASSUME_ALIGNMENT'
    Echo "ASSUME_ALIGNMENT$semicolon"
    Echo '#endif'
}

#------------------------------------------------------------------------------
function SubroutineBegin
{
    Include cg_${version}_${sctype}.h

    case $lang in
        C) Echo '{' ;;
    esac

    IndentPush
    Integer i, $j_fwd, $j_bwd
    Declarations_x
    TemporaryVariables "$real $colons" "$semicolon"
    echo

    case $lang in
        C)  
            Echo 'DEFINE_ADDITIONAL_POINTERS;'
            Echo 'SHIFT_POINTERS;'
            ;;
    esac
    InitMask_x
    Alignment
}

#------------------------------------------------------------------------------
function SubroutineEnd
{
    IndentPop

    case $lang in
        C)       Echo '}' ;;
        Fortran) Echo 'end' ;;
    esac
}

#------------------------------------------------------------------------------
function Timing  # Usage: Timing {START|STOP}
{
    if ((block)); then return; fi
    if ((rkind != 8)); then return; fi

    Echo "TIMING_$1(timing_bin_$timing_bin)$semicolon"
}
   
#------------------------------------------------------------------------------
function TraceEntry
{
    Echo "TRACE_ENTRY$semicolon"
}

#------------------------------------------------------------------------------
function TraceReturn
{
    Echo "TRACE_RETURN$semicolon"
}

#------------------------------------------------------------------------------
function OpenMpDeclarations
{
    if ((block)); then return; fi

    echo
    case $lang in
        C)
            Echo               '#pragma omp parallel for \'
            TemporaryVariables "$nindent"'private(' ') \'
            OpenMpDeclarations_x
            Echo               "$nindent"'private'"($j_fwd, $j_bwd)"
            ;;
        Fortran)
            Echo               '!$omp parallel do &'
            TemporaryVariables '!$omp private(' ') &'
            OpenMpDeclarations_x
            Echo               '!$omp private'"($j_fwd, $j_bwd)"
            ;;
    esac
}

#------------------------------------------------------------------------------
function LoopBegin
{
    if ((block)); then
        case $lang in
            C)       Echo 'for (i = *i1; i <= *i2; i++) {' ;;
            Fortran) Echo 'do i = i1, i2' ;; 
        esac
    else
        case $lang in
            C)       Echo 'for (i = 1; i <= *volh; i++) {' ;;
            Fortran) Echo 'do i = 1, volh' ;; 
        esac
    fi

    IndentPush
}

#------------------------------------------------------------------------------
function LoopEnd
{
    IndentPop

    case $lang in
        C)       Echo '}' ;;
        Fortran) Echo 'enddo' ;;
    esac
}

#------------------------------------------------------------------------------
function TemporaryVariables
{
    typeset beginning_of_line=$1
    typeset end_of_line=$2

    typeset ri i j k dir
    typeset comma=

    echo -n "$indent$beginning_of_line"
    comma=
    for i in 1 2; do
    for dir in bwd fwd; do
    for ri in re im; do
        echo -n "$comma ${ri}_A${i}_${dir}"
        comma=,
    done
    done
    done
    echo " $end_of_line"

    for k in 1 2 3; do
    echo -n "$indent$beginning_of_line"
    comma=
    for i in 1 2; do
    for dir in bwd fwd; do
    for ri in re im; do
        echo -n "$comma ${ri}_B${i}_${dir}${k}"
        comma=,
    done
    done
    done
    echo " $end_of_line"
    done

    for k in 1 2 3; do
    for j in 1 2; do
    echo -n "$indent$beginning_of_line"
    comma=
    for i in 1 2; do
    for ri in re im; do
        echo -n "$comma ${ri}_Out_$i$j$k"
        comma=,
    done
    done
    echo " $end_of_line"
    done
    done
}

#------------------------------------------------------------------------------
function Projection
{
    typeset -i k=$1  # colour

    typeset dir=$2   # bwd | fwd 

   if ((k == 1)); then
       GetNeighbourIndex $dir
       echo
   fi

   case $version in
       d2)  ProjectionD2 "$@";;
       d21) ProjectionD21 "$@";;
   esac
}

#------------------------------------------------------------------------------
function ProjectionD2
{
    typeset -i k=$1  # colour

    typeset dir=$2   # bwd | fwd 

    typeset ADD SUB ONE TWO

    case $dir in
        bwd) ADD=$sub; SUB=$add; ONE=1; TWO=2;;
        fwd) ADD=$add; SUB=$sub; ONE=2; TWO=1;;
    esac

    if [[ $simd != 0 && $mu = x ]]; then
        if [[ $version != projection ]]; then 
            ProjectionD2_x
            return
	fi
    fi

    case $mu in
        x)
            Echo "re_A1_$dir = $ADD(Load(reIN_$dir(1,1,$k)), Load(imIN_$dir(2,2,$k)));"
            Echo "im_A1_$dir = $SUB(Load(imIN_$dir(1,1,$k)), Load(reIN_$dir(2,2,$k)));"
            Echo "re_A2_$dir = $ADD(Load(reIN_$dir(2,1,$k)), Load(imIN_$dir(1,2,$k)));"
            Echo "im_A2_$dir = $SUB(Load(imIN_$dir(2,1,$k)), Load(reIN_$dir(1,2,$k)));"
            ;;
        y)
            Echo "re_A1_$dir = $SUB(Load(reIN_$dir(1,1,$k)), Load(reIN_$dir(2,2,$k)));"
            Echo "im_A1_$dir = $SUB(Load(imIN_$dir(1,1,$k)), Load(imIN_$dir(2,2,$k)));"
            Echo "re_A2_$dir = $ADD(Load(reIN_$dir(2,1,$k)), Load(reIN_$dir(1,2,$k)));"
            Echo "im_A2_$dir = $ADD(Load(imIN_$dir(2,1,$k)), Load(imIN_$dir(1,2,$k)));"
            ;;
        z)
            Echo "re_A1_$dir = $ADD(Load(reIN_$dir(1,1,$k)), Load(imIN_$dir(1,2,$k)));"
            Echo "im_A1_$dir = $SUB(Load(imIN_$dir(1,1,$k)), Load(reIN_$dir(1,2,$k)));"
            Echo "re_A2_$dir = $SUB(Load(reIN_$dir(2,1,$k)), Load(imIN_$dir(2,2,$k)));"
            Echo "im_A2_$dir = $ADD(Load(imIN_$dir(2,1,$k)), Load(reIN_$dir(2,2,$k)));"
            ;;
        t)
            if ((dagger)); then
                Echo "re_A1_$dir = Load(reIN_$dir(1,$TWO,$k));"
                Echo "im_A1_$dir = Load(imIN_$dir(1,$TWO,$k));"
                Echo "re_A2_$dir = Load(reIN_$dir(2,$TWO,$k));"
                Echo "im_A2_$dir = Load(imIN_$dir(2,$TWO,$k));"
            else
                Echo "re_A1_$dir = Load(reIN_$dir(1,$ONE,$k));"
                Echo "im_A1_$dir = Load(imIN_$dir(1,$ONE,$k));"
                Echo "re_A2_$dir = Load(reIN_$dir(2,$ONE,$k));"
                Echo "im_A2_$dir = Load(imIN_$dir(2,$ONE,$k));"
            fi
            ;;
    esac
    echo
}

#------------------------------------------------------------------------------
function ProjectionD21
{
    typeset -i k=$1  # colour

    typeset dir=$2   # bwd | fwd 

    if [[ $simd != 0 && $mu = x ]]; then
        ProjectionD21_x
    else
        Echo "re_A1_$dir = Load(reIN_$dir(1,1,$k));"
        Echo "im_A1_$dir = Load(imIN_$dir(1,1,$k));"
        Echo "re_A2_$dir = Load(reIN_$dir(2,1,$k));"
        Echo "im_A2_$dir = Load(imIN_$dir(2,1,$k));"
    fi
}

#------------------------------------------------------------------------------
function GetNeighbourIndex  # Usage: GetNeighbourIndex {bwd|fwd}
{
    case $lang in
        C)       eval Echo "\"\$j_$1 = nn_$1[i];\"" ;;
        Fortran) eval Echo "\"\$j_$1 = nn_$1(i)\"" ;;
    esac

    InitJ_x $1
}

#------------------------------------------------------------------------------
function Multiplication
{
    typeset -i k=$1  # colour
    typeset -i i j

    typeset dir=$2   # bwd | fwd 

    typeset FMADD FMSUB
    typeset reB imB reU imU reA imA

    case $dir in
        bwd) FMADD=Fmsub; FMSUB=Fmadd;;
        fwd) FMADD=Fmadd; FMSUB=Fmsub;;
    esac
    
    for i in 1 2 3; do
        case $dir in
            bwd) imU="Load(imU_$dir($k,$i))";;
            fwd) imU="Load(imU_$dir($i,$k))";;
        esac
    for j in 1 2; do
        reB=re_B${j}_$dir$i
        imB=im_B${j}_$dir$i
        reA=re_A${j}_$dir
        imA=im_A${j}_$dir

        if ((k == 1)); then
            Echo "$reB = Mul($imU, $imA);"
            Echo "$imB = Mul($imU, $reA);"
        else
            Echo "$reB = $FMSUB($imU, $imA, $reB);"
            Echo "$imB = $FMADD($imU, $reA, $imB);"
        fi
    done
    echo
    done
    echo

    for i in 1 2 3; do
        case $dir in
            bwd) reU="Load(reU_$dir($k,$i))";;
            fwd) reU="Load(reU_$dir($i,$k))";;
        esac
    for j in 1 2; do
        reB=re_B${j}_$dir$i
        imB=im_B${j}_$dir$i
        reA=re_A${j}_$dir
        imA=im_A${j}_$dir

        Echo "$reB = $FMSUB($reU, $reA, $reB);"
        Echo "$imB = $FMADD($reU, $imA, $imB);"
    done
    echo
    done
    echo
}

#------------------------------------------------------------------------------
function BackProjectionStore
{
    if ((first)); then
        Echo "Store($1, $2);"
    else
        Echo "Store($1, Add($2, Load($1)));"
    fi
}

#------------------------------------------------------------------------------
function BackProjection
{
    typeset -i k=$1  # colour
    typeset -i i j

    typeset action=$2 # compute | store
    typeset fb ri ir
    typeset out OUT b_bwd b_fwd b_fb
    typeset arg1 arg2

    case $mu in
        x|y|z)
            for i in 1 2; do
            for ri in re im; do
                out="${ri}_Out_${i}1$k"
                OUT="${ri}OUT($i,1,$k)"
                b_bwd=${ri}_B${i}_bwd$k
                b_fwd=${ri}_B${i}_fwd$k

                case $action in
                    compute) Echo "$out = Add($b_bwd, $b_fwd);";;
                    store) BackProjectionStore $OUT $out;;
                esac
            done
            done
            echo

            for i in 1 2; do
                j=$((1 + 2 - i))
            for ri in re im; do
                case $ri in
                    re) ir=im;;
                    im) ir=re;;
                esac
        
                out="${ri}_Out_${i}2$k"
                OUT="${ri}OUT($i,2,$k)"
        
                case $mu in
                    x)
                        b_bwd=${ir}_B${j}_$bwd$k
                        b_fwd=${ir}_B${j}_$fwd$k
                        case $ri in
                            re) arg1=$b_bwd; arg2=$b_fwd;;
                            im) arg1=$b_fwd; arg2=$b_bwd;;
                        esac
                        ;;     
        
                    y)
                        b_bwd=${ri}_B${j}_$bwd$k
                        b_fwd=${ri}_B${j}_$fwd$k
                        case $i in
                            1) arg1=$b_fwd; arg2=$b_bwd;;
                            2) arg1=$b_bwd; arg2=$b_fwd;;
                        esac
                        ;;
        
                    z)
                        b_bwd=${ir}_B${i}_$bwd$k
                        b_fwd=${ir}_B${i}_$fwd$k
                        case $i in
                            1) case $ri in
                                 re) arg1=$b_bwd; arg2=$b_fwd;;
                                 im) arg1=$b_fwd; arg2=$b_bwd;;
                               esac
                               ;;
                            2) case $ri in
                                 re) arg1=$b_fwd; arg2=$b_bwd;;
                                 im) arg1=$b_bwd; arg2=$b_fwd;;
                               esac
                               ;;
                        esac
                        ;;
                esac
        
                case $action in
                    compute) Echo "$out = Sub($arg1, $arg2);";;
                    store) BackProjectionStore $OUT $out;;
                esac
            done
            done
            echo
            ;;

        t)
            for j in 1 2; do
                case $j in
                    1) fb=$bwd;; 
                    2) fb=$fwd;;
                esac
            for i in 1 2; do
            for ri in re im; do
                out="${ri}_Out_$i$j$k"
                OUT="${ri}OUT($i,$j,$k)"
                b_fb=${ri}_B${i}_$fb$k

                case $action in
                    store) BackProjectionStore $OUT $b_fb;;
                    ###compute) Echo "$out = Mul(Two, $b_fb);";;
                    ###store) BackProjectionStore $OUT $out;;
                esac
            done
            done
            echo
            done
            ;;
    esac
    echo
}

#------------------------------------------------------------------------------
function Mu  # Usage: Mu $mu
{
   case $mu in
        x) echo 1 ;;
        y) echo 2 ;;
        z) echo 3 ;;
        t) echo 4 ;;
   esac
}

#==============================================================================
# work:

source ./cg_d_x.sh

#------------------------------------------------------------------------------
# 'd' routines

if [[ $version = d* ]]; then
    Header
    SubroutineBegin
    TraceEntry
    Timing START
    OpenMpDeclarations
    LoopBegin

    for colour in 1 2 3; do
	for dir in bwd fwd; do
	    Projection $colour $dir
	    Multiplication $colour $dir
	done
    done

    for colour in 1 2 3; do
	BackProjection $colour compute
	BackProjection $colour store
    done

    LoopEnd
    Timing STOP
    TraceReturn
    SubroutineEnd
fi

#------------------------------------------------------------------------------
# 'projection' - project the whole field 
# and  'proj' - project boundary only

if [[ $version = proj* ]]; then

   if [[ $mu = x ]]; then Die "'proj' not implemented for mu=x"; fi
   if [[ $lang != Fortran ]]; then Die "'proj' needs Fortran"; fi

   typeset sctype2

   case $sctype in
       sc) sctype2=sc422;;
       sc22) sctype2=sc22;;
   esac

   if ((simd)); then 
      if [[ $simdflag != *GENERIC* ]]; then
         Die "'proj' needs simdflag=*GENERIC*"
      else
          Ifndef USE_SIMD_GENERIC
	  Define USE_SIMD_GENERIC
          EndIfdef
      fi
   fi
   Header
   Define a_fwd a
   Define a_bwd a
   Define SpincolIN Spincol_$sctype2
   echo
fi

if [[ $version = proj ]]; then
   Define jf j
   Define jb j
   Echo "subroutine NAME(proj, a)"; IndentPush
   Echo "use module_function_decl"
   Echo "use T(module_surface)"
   Echo "use T(module_vol)"
   Echo "use typedef_simd"
   Echo "implicit none"
   Echo "sc2_FIELD, intent(out) :: proj"
   Echo "${sctype2}_FIELD, intent(in) :: a"
   Echo "integer :: i, j, i0, j0, count"
   Echo "integer, parameter :: mu = $(Mu $mu)"
   Echo "integer, parameter :: fb = $(ToUpper $fwd_bwd)"
   Echo "integer :: me, first, start, step"
   echo
   Echo "ASSUME_ALIGNED(proj)"
   Echo "ASSUME_ALIGNED(a)"
   echo
   Echo "me = my_thread()"
   Echo "first = first_thread()"
   Echo "if (me < first) return"
   Ifdef TRACE
   Echo "if (me == first) TRACE_ENTRY"
   EndIfdef
   Ifdef TIMING
   Echo "if (me == first) TIMING_START(timing_bin_cg_proj_$mu)"
   EndIfdef
   Echo "start = me - first  ! starts at 0"
   Echo "step = active_threads() - first"
   echo
   Echo "do count = start, surface2(mu)%count - 1, step"; IndentPush
   Echo "i0 = count * surface2(mu)%blocklength"
   Echo "j0 = surface2(mu)%i_start(fb) + count * surface2(mu)%stride - 1"
   echo
   Echo "do i = 1, surface2(mu)%blocklength"; IndentPush  
   Echo "j = j0 + i"
   for colour in 1 2 3; do
       ProjectionD2 $colour $fwd_bwd | sed "
        s/re_A1_[fb]wd = /Store(Real(proj(1, $colour, i0 + i, mu, fb)), /
        s/im_A1_[fb]wd = /Store(Imag(proj(1, $colour, i0 + i, mu, fb)), /
        s/re_A2_[fb]wd = /Store(Real(proj(2, $colour, i0 + i, mu, fb)), /
        s/im_A2_[fb]wd = /Store(Imag(proj(2, $colour, i0 + i, mu, fb)), /
        s/;/\)/"
   echo
   done
   IndentPop; Echo "enddo"
   IndentPop; Echo "enddo"
              Ifdef TIMING
              Echo "if (me == first) TIMING_STOP(timing_bin_cg_proj_$mu)"
              EndIfdef
              Ifdef TRACE
              Echo "if (me == first) TRACE_RETURN"
              EndIfdef
   IndentPop; Echo "end"
fi

if [[ $version = projection ]]; then
   Define jf i
   Define jb i
   Echo "subroutine NAME(projection, a, varsBLOCK)"; IndentPush
   Echo "use typedef_simd"
   Echo "implicit none"
   Echo "integer, intent(in) :: varsBLOCK"
   Echo "sc2_FIELD, intent(out) :: projection"
   Echo "${sctype2}_FIELD, intent(in) :: a"
   Echo "integer :: i"
   echo
   Echo "ASSUME_ALIGNED(a)"
   Echo "ASSUME_ALIGNED(projection)"
   echo  
   Echo "TRACE_ENTRY"
   Echo "TIMING_START(timing_bin_cg_projection)"
   echo
   if ((block)); then 
      Echo "do i = i1, i2"; IndentPush
   else
      Echo '!$omp parallel do'
      Echo "do i = 1, volh"; IndentPush
   fi
   for colour in 1 2 3; do
   for mu in x y z t; do
   for fb in fwd bwd; do
       ProjectionD2 $colour $fb | sed "
        s/re_A1_$fb = /Store(Real(projection(1, $colour, i, $(Mu $mu), $(ToUpper $fb))), /
        s/im_A1_$fb = /Store(Imag(projection(1, $colour, i, $(Mu $mu), $(ToUpper $fb))), /
        s/re_A2_$fb = /Store(Real(projection(2, $colour, i, $(Mu $mu), $(ToUpper $fb))), /
        s/im_A2_$fb = /Store(Imag(projection(2, $colour, i, $(Mu $mu), $(ToUpper $fb))), /
        s/;/\)/
        "
   done
   done
   done
   IndentPop; Echo "enddo"
   Echo "TIMING_STOP(timing_bin_cg_projection)"
   Echo "TRACE_RETURN"
   IndentPop; Echo "end"
fi

exit
