#!/bin/sh
set -euC

##/// glslrun 1.0
#////
#//// Run GLSL code snippets on the CPU.
#////
###// usage:
#////   glslrun [options] [--] [<file>]
#////   glslrun -h|--help
#////   glslrun --version
#////
###// arguments:
####/   <file>
#////     File containing code to run. If <file> is omitted or -, to read from
#////     stdin instead.
#////
###// options:
####/   -m, --main
#////     Generate an enclosing main() function.
#////
####/   -p, --print
#////     Print the generated code that is passed to the compiler.

## Messages
help()    { sed -n 's|^#[#/]*/ \?||p' "$0";              exit 0;    }
version() { help | awk '/^$/{++p;next}p==0';             exit 0;    }
usage()   { help | awk '/^$/{++p;next}p==2';             exit 0;    }
parse()   { printf '%s: error: %s\n'   "$0" "$1"; usage; exit 1;    } >&2
error()   { printf '%s: error: %s\n'   "$0" "$1";        exit 1;    } >&2
warning() { printf '%s: warning: %s\n' "$0" "$1";                   } >&2
opt()     { [ $# -gt 1 ] || parse "option '$1' value not provided"; }
arg()     { [ $# -gt 1 ] || parse "argument '$1' not provided";     }

## Parse special options
case "${1-}"
in
  '-h'|'--help') help; ;;
  '--version') version; ;;
esac

## Parse options
main=''
print=''
while [ $# -gt 0 ]
do
  case "$1"
  in
    '-m'|'--main') main='y'; ;;
    '-p'|'--print') print='y'; ;;
    '--') shift; break; ;;
    '-'?*) parse "unrecognized option '$1'"; ;;
    *) break; ;;
  esac
  shift
done

## Parse optional arguments
file="${1-}"; shift $(($#>0));

## Parse unrecognized arguments
[ $# -eq 0 ] || parse "unrecognized argument: '$1'"

## Create temporary directory
dir_tmp="$(mktemp -d)"
trap 'rm -rf "$dir_tmp"' EXIT

## Variables
file_in="$dir_tmp/$(basename "$0").cpp"
file_out="$file_in-out"
if [ "$main" ]
then
  main_begin="int main() {"
  main_end="}"
fi
if [ "$file" = "-" ]
then
  file=""
fi

## Generate
cat << EOF > "$file_in"
#include <cassert>
#include <iostream>

#define GLM_FORCE_SWIZZLE 1
#define GLM_ENABLE_EXPERIMENTAL 1
#include <glm/glm.hpp>
#include <glm/gtx/io.hpp>

using namespace std;
using namespace glm;

// Constants
const int gl_MaxVertexAttribs = 16;
const int gl_MaxVertexOutputComponents = 64;
const int gl_MaxVertexUniformComponents = 1024;
const int gl_MaxVertexTextureImageUnits = 16;
const int gl_MaxGeometryInputComponents = 64;
const int gl_MaxGeometryOutputComponents = 128;
const int gl_MaxGeometryUniformComponents = 1024;
const int gl_MaxGeometryTextureImageUnits = 16;
const int gl_MaxGeometryOutputVertices = 256;
const int gl_MaxGeometryTotalOutputComponents = 1024;
const int gl_MaxGeometryVaryingComponents = 64;
const int gl_MaxFragmentInputComponents = 128;
const int gl_MaxDrawBuffers = 8;
const int gl_MaxFragmentUniformComponents = 1024;
const int gl_MaxTextureImageUnits1 = 16;
const int gl_MaxClipDistances = 8;
const int gl_MaxCombinedTextureImageUnits = 48;
// OpenGL 4.0
int gl_MaxTessControlInputComponents = 128;
int gl_MaxTessControlOutputComponents = 128;
int gl_MaxTessControlUniformComponents = 1024;
int gl_MaxTessControlTextureImageUnits = 16;
int gl_MaxTessControlTotalOutputComponents = 4096;
int gl_MaxTessEvaluationInputComponents = 128;
int gl_MaxTessEvaluationOutputComponents = 128;
int gl_MaxTessEvaluationUniformComponents = 1024;
int gl_MaxTessEvaluationTextureImageUnits = 16;
int gl_MaxTessPatchComponents = 120;
int gl_MaxPatchVertices = 32;
int gl_MaxTessGenLevel = 64;
// OpenGL 4.1
int gl_MaxViewports = 16;
int gl_MaxVertexUniformVectors = 256;
int gl_MaxFragmentUniformVectors = 256;
int gl_MaxVaryingVectors = 15;
// OpenGL 4.2
int gl_MaxVertexImageUniforms = 0;
int gl_MaxVertexAtomicCounters = 0;
int gl_MaxVertexAtomicCounterBuffers = 0;
int gl_MaxTessControlImageUniforms = 0;
int gl_MaxTessControlAtomicCounters = 0;
int gl_MaxTessControlAtomicCounterBuffers = 0;
int gl_MaxTessEvaluationImageUniforms = 0;
int gl_MaxTessEvaluationAtomicCounters = 0;
int gl_MaxTessEvaluationAtomicCounterBuffers = 0;
int gl_MaxGeometryImageUniforms = 0;
int gl_MaxGeometryAtomicCounters = 0;
int gl_MaxGeometryAtomicCounterBuffers = 0;
int gl_MaxFragmentImageUniforms = 8;
int gl_MaxFragmentAtomicCounters = 8;
int gl_MaxFragmentAtomicCounterBuffers = 1;
int gl_MaxCombinedImageUniforms = 8;
int gl_MaxCombinedAtomicCounters = 8;
int gl_MaxCombinedAtomicCounterBuffers = 1;
int gl_MaxImageUnits = 8;
int gl_MaxCombinedImageUnitsAndFragmentOutputs = 8;
int gl_MaxImageSamples = 0;
int gl_MaxAtomicCounterBindings = 1;
int gl_MaxAtomicCounterBufferSize = 32;
int gl_MinProgramTexelOffset = -8;
int gl_MaxProgramTexelOffset = 7;
// OpenGL 4.3
ivec3 gl_MaxComputeWorkGroupCount = {65535, 65535, 65535};
ivec3 gl_MaxComputeWorkGroupSize = { 1024, 1024, 64 };
int gl_MaxComputeUniformComponents = 512;
int gl_MaxComputeTextureImageUnits = 16;
int gl_MaxComputeImageUniforms = 8;
int gl_MaxComputeAtomicCounters = 8;
int gl_MaxComputeAtomicCounterBuffers = 1;
// OpenGL 4.4
int gl_MaxTransformFeedbackBuffers = 4;
int gl_MaxTransformFeedbackInterleavedComponents = 64;

// Vertex shader inputs
int  gl_VertexID;
int  gl_InstanceID;
int  gl_DrawID;
int  gl_BaseVertex;
int  gl_BaseInstance;
// Vertex shader outputs
vec4  gl_Position;
float gl_PointSize;
float gl_ClipDistance[gl_MaxClipDistances];
// Tessellation control shader inputs
int gl_PatchVerticesIn;
// Tessellation control shader outputs
float gl_TessLevelOuter[4];
float gl_TessLevelInner[2];
// Tessellation evaluation shader inputs
vec3 gl_TessCoord;
// Geometry shader inputs
int gl_PrimitiveIDIn;
int gl_InvocationID;
// Geometry shader outputs
int gl_PrimitiveID;
int gl_Layer;
int gl_ViewportIndex;
// Fragment shader inputs
vec4 gl_FragCoord;
bool gl_FrontFacing;
vec2 gl_PointCoord;
int gl_SampleID;
vec2 gl_SamplePosition;
int gl_SampleMaskIn[1];
// Fragment shader outputs
float gl_FragDepth;
int gl_SampleMask;
// Compute shader inputs
uvec3 gl_NumWorkGroups;
uvec3 gl_WorkGroupID;
uvec3 gl_LocalInvocationID;
uvec3 gl_GlobalInvocationID;
uint  gl_LocalInvocationIndex;
// Compute shader other variables
uvec3 gl_WorkGroupSize;
// Shader uniforms
struct gl_DepthRange
{
    float near;
    float far;
    float diff;
};
int gl_NumSamples;

// Functions
void EmitVertex() {}
void EndPrimitive() {}

#define GLSLRUN 1

${main_begin-}
#line 1
$(
    sed ${file:+"$file"} \
        -e 's/^\s*#version\>.*//' \
        -e 's/^\s*#extension\>.*//' \
        -e 's/^\s*layout\>.*//' \
        -e 's/^\s*in\>\s\?//' \
        -e 's/^\s*out\>\s\?//' \
        -e 's/^\s*void\(\s\+main\>\)/int\1/' \
)
${main_end-}
EOF

## Optionally print
if [ "$print" ]
then
  cat "$file_in"
fi

## Compile
"${CXX-g++}" -Wall -I. -o "$file_out" "$file_in"

## Run
"$file_out"
