#! /usr/bin/gawk -f
# Last edited on 2005-01-05 23:18:34 by stolfi

BEGIN {
  usage = ( ARGV[0] " -v junk=JUNKFILE < INFILE > OUTFILE" );
  abort = -1;
  # Reads a ".pov" or ".inc" file.
  # Removes all camera and light_source commands,
  # replacing them by a call to the "camlight" macro.
  # Writes the deleted text to "JUNKFILE" (default "junk.inc")
  if (junk == "") { junk = "junk.inc"; }
  cmtstate = 0;  # 0 = plain, 1 = inside "/*..*/"
  brastate = 0;  # 0 = plain, 1 = waiting opeing "{".
  povstate = 0;  # 0 = plain, 1 = inside "camera", 2 = "light source"
  hadcam = 0; # TRUE if any "camera" command was found.
  reset_camera_params();
  
  # Output standard header:
  printf "// Last edited on DATE TIME by USER\n";
  printf "// Processed by remove-cam-lights\n";
  eol = 1; # TRUE if last char written to {stdout} is "\n"
}

function reset_camera_params()
{
  # Resets the camera parameters {cam_{ctr,loc,dir,sky,rot}}: 
  cam_ctr = "??"; # camera's "look_at"
  cam_loc = "??"; # camera's "location"
  cam_sky = "??"; # camera's "cam_sky"
  cam_dir = "??"; # camera's "direction"
  cam_rot = "??"; # camera's "rotation"
}  

(abort >= 0) { exit abort; }

// {
  lin = $0;
  # Line cleanup:
  gsub(/[\011]/, " ", lin);
  gsub(/[\015]/, "", lin);
  
  # Process line up to but not including the final "\n".
  # However we write "\n" to {junk} after each "*/",
  # at the end of each "//" comment, and at the end
  # of each camera/light specification.
  while (lin != "")
    { if (cmtstate == 1)
        { # inside "/*...*/" comment:
          if (match(lin, /[*][\/]/))
            { # Split {lin} just after the "*/":
              bef = substr(lin,1,RSTART+RLENGTH-1);
              lin = substr(lin,RSTART+RLENGTH);
              # Part before "*/" is junk:
              printf "%s\n", bef > junk;
              cmtstate = 0;
            }
          else
            { printf "%s", lin > junk; lin = ""; }
        }
      else if (brastate == 1)
        { # Expecting a "{" after "camera" or "light_source":
          if (match(lin, /^[ ]+/))
            { # Split blanks off, remain in same state:
              bef = substr(lin,1,RSTART+RLENGTH-1);
              lin = substr(lin,RSTART+RLENGTH);
              printf "%s", bef;
            }
          else if (match(lin, /^([\/][\/]|[\/][*]|[{])/))
            { # Get the matched string {key} and split {lin} at it:
              key = substr(lin,RSTART,RLENGTH);
              lin = substr(lin,RSTART+RLENGTH);
              if (key == "{")
                { printf "%s", key > junk; brastate = 0; }
              else if (key == "//")
                { printf "//%s\n", lin > junk; lin = ""; }
              else if (key == "/*")
                { printf "/*" > junk; cmtstate = 1; }
              else
                { prog_error("bad key"); }
            }
          else
            { data_error("missing \"{\" after camera or light_source"); } 
        }
      else if (povstate == 1)
        { # Inside "camera" command
          if (match(lin, /([\/][\/]|[\/][*]|[{}]|location|sky|look_at|rotate|direction)/))
            { # Get the matched string {key} and split {lin} at it:
              bef = substr(lin,1,RSTART-1);
              key = substr(lin,RSTART,RLENGTH);
              lin = substr(lin,RSTART+RLENGTH);
              # Part before {key} is always junk:
              printf "%s", bef > junk;
              if (key == "location")
                { printf "%s", key > junk; cam_loc = get_camera_param(); }
              else if (key == "sky")
                { printf "%s", key > junk; cam_sky = get_camera_param(); }
              else if (key == "look_at")
                { printf "%s", key > junk; cam_ctr = get_camera_param(); }
              else if (key == "rotate")
                { printf "%s", key > junk; cam_rot = get_camera_param(); }
              else if (key == "direction")
                { printf "%s", key > junk; cam_dir = get_camera_param(); }
              else if (key == "//")
                { printf "//%s\n", lin > junk; lin = ""; }
              else if (key == "/*")
                { printf "/*" > junk; cmtstate = 1; }
              else if (key == "}")
                { printf "}\n" > junk; povstate = 0; }
              else if (key == "{")
                { data_error("found \"{\" inside camera spec"); }
              else
                { prog_error("bad key"); }
            }
          else
            { printf "%s", lin > junk; lin = ""; }
        }
      else if (povstate == 2)
        { # Inside "light_source" directive
          if (match(lin, /([\/][\/]|[\/][*]|[{}])/))
            { # Get the matched string {key} and split {lin} at it:
              bef = substr(lin,1,RSTART-1);
              key = substr(lin,RSTART,RLENGTH);
              lin = substr(lin,RSTART+RLENGTH);
              # Part before {key} is always junk:
              printf "%s", bef > junk;
              if (key == "//")
                { printf "//%s\n", lin > junk; lin = ""; }
              else if (key == "/*")
                { printf "/*" > junk; cmtstate = 1; }
              else if (key == "}")
                { printf "}\n" > junk; povstate = 0; }
              else if (key == "{")
                { data_error("found \"{\" inside light source spec"); }
              else
                { prog_error("bad key"); }
            }
          else
            { printf "%s", lin > junk; lin = ""; }
        }
      else 
        { # Normal state ({povstate == 0, cmtstate == 0})
          if (match(lin, /([\/][\/]|[\/][*]|camera|light_source)/))
            { # Get the matched string {key} and split {lin} at it:
              bef = substr(lin,1,RSTART-1);
              key = substr(lin,RSTART,RLENGTH);
              lin = substr(lin,RSTART+RLENGTH);
              # Print part before {key}:
              printf "%s", bef;
              # Decide next state
              if (key == "camera")
                { povstate = 1; brastate = 1;
                  if (hadcam) 
                    { data_warning("multiple cameras"); }
                  hadcam = 1;
                  reset_camera_params();
                  printf "%s", key > junk;
                }
              else if (key == "light_source")
                { povstate = 2; brastate = 1;
                  printf "%s", key > junk;
                }
              else if (key == "//")
                { printf "//%s\n", lin > junk; lin = ""; }
              else if (key == "/*")
                { cmtstate = 1; printf "/*" > junk; }
              else
                { prog_error("bad key"); }
            }
          else
            { printf "%s", lin; lin = ""; }
        }
    }
  # Process end-of-line:
  if ((povstate == 0) && (cmtstate == 0))
    { printf "\n"; }
  else
    { printf "\n" > junk; }
}

function get_camera_param(   par,tag)
{
  # Extracts from the beginning of {lin} a camera parameter,
  # up to the next parameter keyword ("up", "right", etc.), comment,
  # or "}". Returns the parameter as result, writes the parsed
  # portion of {lin} to {junk}, and leaves the remainder in {lin}.
  # For now, require parameter to be on the same line as the keyword,
  # with no intervening comments.

  if (match(lin, /([\/][\/]|[\/][*]|[{}]|location|cam_sky|look_at|right|up|angle|rotate|direction)/))
    { # Get the matched string {tag} and split {lin} at it:
      par = substr(lin,1,RSTART-1);
      tag = substr(lin,RSTART,RLENGTH);
      lin = substr(lin,RSTART);
      if (tag == "{")
        { data_error("found \"{\" inside camera spec"); }
    }
  else
    { par = lin; lin = ""; }

  # The {par} string is junk:
  printf "%s", par > junk;
  # Cleanup the {par} string:
  gsub(/^[ ]+/, "", par);
  gsub(/[ ]+$/, "", par);
  gsub(/[ ][ ]+/, " ", par);
  gsub(/[ ][,]/, ",", par);
  gsub(/[,][ ]/, ",", par);
  gsub(/[ ][>]/, ">", par);
  gsub(/[<][ ]/, "<", par);
  # Check for missing parameter value:
  if (par == "")
      { data_error("missing camera parameter value"); return "??"; }
    else
      { return par; }
}
    
END {
  if (abort >= 0) { exit abort; }

  # Check for proper closing of things:
  if (cmtstate != 0)
    { param_error("unterminated \"/*...*/\" comment"); }
  else if (brastate != 0)
    { param_error("missing \"{\" after \"camera\" or \"light_source\""); }
  else if (povstate != 0)
    { param_error("unterminated camera or light source spec"); }
  
  if (hadcam == 0)
    { param_warning("no camera spec found"); }
  else
    { # Cleanup camera parameters, generate standard macro call:
    
      # Require {cam_sky} to be Y or Z:
      if (cam_sky == "??")
        { param_warning("missing camera \"sky\" parameter"); cam_sky = "y"; }
      else if (cam_sky !~ /^[yz]$/)
        { param_warning(("non-standard cam_sky \"" cam_sky "\""));
          cam_sky = ( "(" cam_sky ")" );
        }
      
      # Require {cam_ctr}, protect it:
      if (cam_ctr == "??")
        { param_warning("missing camera \"look_at\" parameter");
          cam_ctr = "<0,0,0>"; 
        }
      else if (cam_ctr ~ /^[<][-+ 0.]*[,][-+ 0.]*[,][-+ 0.]*[>]$/)
        { cam_ctr = "<0,0,0>"; }
      else if (cam_ctr !~ /^[<][-+ 0-9.]*[,][-+ 0-9.]*[,][-+ 0-9.]*[>]$/)
        { cam_ctr = ( "(" cam_ctr ")" ); }
      
      # Build the camera vector {cam_vec}:
      if (cam_loc != "??")
        { # Protect the camera location {cam_loc}:
          if (cam_loc !~ /^[<][-+ 0-9.]*[,][-+ 0-9.]*[,][-+ 0-9.]*[>]$/)
            { cam_loc = ( "(" cam_loc ")" ); }
          # Build the camera vector as {cam_loc - cam_ctr}:
          if (cam_ctr == "<0,0,0>")
            { cam_vec = cam_loc; }
          else 
            { cam_vec = ( cam_loc "-" cam_ctr ); }
        }
      else if (cam_dir != "??")
        { # Take the camera direction as the camera vector:
          cam_vec = cam_dir; 
        }
      else
        { param_warning("missing camera \"location\" and \"direction\" parameter");
          cam_vec = "<10,10,10>";
        }

      # Rotate the camera is so specified:
      if (cam_rot != "??")
        { cam_vec = ( "vrotate(" cam_vec "," cam_rot ")" ); }
    
      # Add standard camera spec: 
      printf "\n";
      printf "#include \"camlight.inc\"\n";
      printf "camlight(%s,%s,1.00,%s,1.0)\n",cam_ctr,cam_vec,cam_sky;
    }
  close(junk);
}

function arg_error(msg)
{
  printf "** %s\n", msg > "/dev/stderr";
  abort = 1; exit abort;
}

function data_error(msg)
{
  printf "%s:%d: «%s»\n", FILENAME, FNR, $0 > "/dev/stderr";
  printf "%s:%d: ** %s\n", FILENAME, FNR, msg > "/dev/stderr";
  abort = 1; exit abort;
}

function data_warning(msg)
{
  printf "%s:%d: «%s»\n", FILENAME, FNR, $0 > "/dev/stderr";
  printf "%s:%d: warning: %s\n", FILENAME, FNR, msg > "/dev/stderr";
}

function param_error(msg)
{
  printf "%s:%d: ** %s\n", FILENAME, FNR, msg > "/dev/stderr";
  abort = 1; exit abort;
}

function param_warning(msg)
{
  printf "%s:%d: warning: %s\n", FILENAME, FNR, msg > "/dev/stderr";
}

function prog_error(msg)
{
  printf "%s:%d: ** program error: %s\n", FILENAME, FNR, msg > "/dev/stderr";
  abort = 1; exit abort;
}