
/*

  KLayout Layout Viewer
  Copyright (C) 2006-2018 Matthias Koefferlein

  This program 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 2 of the License, or
  (at your option) any later version.

  This program 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 this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

*/


#include "extRS274XReader.h"
#include "extRS274XApertures.h"

namespace ext
{

// ---------------------------------------------------------------------------------
//  RS274XReader implementation

RS274XReader::RS274XReader ()
  : GerberFileReader ()
{
  init ();
}

RS274XReader::~RS274XReader ()
{
  for (std::vector<RS274XApertureBase *>::const_iterator a = m_apertures.begin (); a != m_apertures.end (); ++a) {
    if (*a) {
      delete *a;
    }
  }

  m_apertures.clear ();
}

bool 
RS274XReader::does_accept ()
{
  return true;
}

bool
RS274XReader::is_clear_polarity ()
{
  //  Now that we have used the polarity, we not longer guess it.
  m_guess_polarity = false;
  return m_neg_polarity ? !m_clear : m_clear;
}

void
RS274XReader::init ()
{
  //  Initialize reader:
  m_clear = false;
  m_guess_polarity = true;
  m_neg_polarity = false;
  m_relative = false;
  m_x = m_y = 0.0;
  m_current_gcode = -1;
  m_current_dcode = -1;
  m_polygon_mode = false;
  m_axis_mapping = ab_xy;
  m_current_aperture = 0;
  m_360deg_circular = false;
  m_buffer.clear ();
  m_polygon_points.clear ();

  for (std::vector<RS274XApertureBase *>::const_iterator a = m_apertures.begin (); a != m_apertures.end (); ++a) {
    if (*a) {
      delete *a;
    }
  }
  m_apertures.clear ();
  m_aperture_macros.clear ();
  m_current_aperture = 0;
}

static GerberMetaData::Position
parse_position (tl::Extractor &ex)
{
  if (ex.test ("Bot")) {
    return GerberMetaData::Bottom;
  } else if (ex.test ("Top")) {
    return GerberMetaData::Top;
  } else if (ex.test ("Inr")) {
    return GerberMetaData::Inner;
  } else {
    return GerberMetaData::NoPosition;
  }
}


GerberMetaData
RS274XReader::do_scan ()
{
  GerberMetaData data;

  char c;

  //  Actually read:
  while ((c = stream ().skip ()) != 0 && !stream ().at_end ()) {

    if (c == '%') {

      stream ().get_char ();

      while (! stream ().at_end () && (c = stream ().skip ()) != '%') {

        std::string param;
        param += stream ().get_char ();
        if (! stream ().at_end ()) {
          param += stream ().get_char ();
        }

        std::string bl = get_block ();

        if (param == "TF") {

          //  Extract information
          tl::Extractor ex (bl);

          if (ex.test (".ProjectId")) {

            ex.test (",");
            data.project_id = ex.get ();

          } else if (ex.test (".CreationDate")) {

            ex.test (",");
            data.creation_date = ex.get ();

          } else if (ex.test (".GenerationSoftware")) {

            ex.test (",");
            data.generation_software = ex.get ();

          } else if (ex.test (".FileFunction")) {

            ex.test (",");
            bool plated = false;

            if (ex.test ("Copper")) {

              data.function = GerberMetaData::Copper;

              ex.test (",");
              ex.test ("L");
              ex.read (data.cu_layer_number);
              ex.test (",");
              data.position = parse_position (ex);

            } else if (ex.test ("Profile")) {

              data.function = GerberMetaData::Profile;

            } else if (ex.test ("Soldermask")) {

              data.function = GerberMetaData::SolderMask;
              ex.test (",");
              data.position = parse_position (ex);

            } else if (ex.test ("Legend")) {

              data.function = GerberMetaData::Legend;
              ex.test (",");
              data.position = parse_position (ex);

            } else if ((plated = ex.test ("Plated")) || ex.test ("NonPlated")) {

              data.function = plated ? GerberMetaData::PlatedHole : GerberMetaData::NonPlatedHole;

              ex.test (",");
              ex.read (data.from_cu);
              ex.test (",");
              ex.read (data.to_cu);

            } else {
              data.function = GerberMetaData::NoFunction;
            }

          }

        }

      }

      //  eat trailing '%'
      if (! stream ().at_end ()) {
        stream ().get_char ();
      }

    } else {
      get_block ();
    }

  }

  return data;
}

void
RS274XReader::do_read ()
{
  init ();

  char c;

  //  Actually read:
  while ((c = stream ().skip ()) != 0 && !stream ().at_end ()) {

    if (c == '%') {

      stream ().get_char ();

      while (! stream ().at_end () && (c = stream ().skip ()) != '%') {
        
        std::string param;
        param += stream ().get_char ();

        if (stream ().at_end ()) {
          throw tl::Exception (tl::to_string (QObject::tr ("Unexpected EOF")));
        }

        param += stream ().get_char ();

        if (param == "AS") {
          read_as_parameter (get_block ());
        } else if (param == "FS") {
          read_fs_parameter (get_block ());
        } else if (param == "MI") {
          read_mi_parameter (get_block ());
        } else if (param == "MO") {
          read_mo_parameter (get_block ());
        } else if (param == "OF") {
          read_of_parameter (get_block ());
        } else if (param == "SF") {
          read_sf_parameter (get_block ());
        } else if (param == "IJ") {
          read_ij_parameter (get_block ());
        } else if (param == "IN") {
          read_in_parameter (get_block ());
        } else if (param == "IO") {
          read_io_parameter (get_block ());
        } else if (param == "IP") {
          read_ip_parameter (get_block ());
        } else if (param == "IR") {
          read_ir_parameter (get_block ());
        } else if (param == "PF") {
          read_pf_parameter (get_block ());
        } else if (param == "AD") {
          read_ad_parameter (get_block ());
        } else if (param == "TA" || param == "TD" || param == "TF") {

          //  TA, TD and TF paramters are skipped for layout
          get_block ();

        } else if (param == "AB") {

          std::string dcode = get_block ();
          if (dcode.empty ()) {

            if (graphics_stack_empty ()) {
              throw tl::Exception (tl::to_string (QObject::tr ("AB closed without initial opening AB command")));
            } else {
              db::Region region;
              collect (region);
              std::string ap = pop_state ();
              install_block_aperture (ap, region);
            }

          } else if (m_polygon_mode) {
            warn (tl::to_string (QObject::tr ("AB command inside polygon sequence (G36/G37) - polygon ignored")));
          } else {
            push_state (dcode);
          }

        } else if (param == "AM") {

          //  AM parameters can span multiple data blocks, so collect them

          std::string am_string;
          while (! stream ().at_end () && stream ().skip () != '%') {
            am_string += get_block ();
            am_string += "*";
          }

          read_am_parameter (am_string);

        } else if (param == "KO") {
          read_ko_parameter (get_block ());
        } else if (param == "LN") {
          read_ln_parameter (get_block ());
        } else if (param == "LP") {
          read_lp_parameter (get_block ());
        } else if (param == "LM") {
          read_lm_parameter (get_block ());
        } else if (param == "LR") {
          read_lr_parameter (get_block ());
        } else if (param == "LS") {
          read_ls_parameter (get_block ());
        } else if (param == "SR") {
          read_sr_parameter (get_block ());
        } else if (param == "IF") {
          read_if_parameter (get_block ());
        } else {
          get_block ();
          warn (tl::to_string (QObject::tr ("Parameter ignored: ")) + param);
        }

      }
      
      //  eat trailing '%'
      stream ().get_char ();

    } else {

      //  process block
      bool has_coord = false;
      double x = m_x, y = m_y;
      double i = 0.0, j = 0.0;

      tl::Extractor ex (get_block ().c_str ());

      while (! ex.at_end ()) {

        c = *ex.skip ();
        ++ex;

        if (c == 'M') {

          int mcode = 0;
          ex.read (mcode);
          process_mcode (mcode);

        } else if (c == 'N') {

          int ncode = 0;
          ex.read (ncode);
          //  currently N codes are ignored.

        } else if (c == 'G') {

          int gcode = -1;
          ex.read (gcode);

          if (gcode == 4) {

            // .. G04 - ignore rest of block
            break;

          } else if (gcode == 36) {

            // .. G36 - enter polygon mode
            m_polygon_mode = true;
            m_polygon_points.clear ();
            m_current_gcode = 1;
            m_current_dcode = -1;

          } else if (gcode == 37) {

            // .. G37 - leave polygon mode
            m_polygon_mode = false;

            if (m_polygon_points.size () >= 3) {
              db::DPolygon poly;
              poly.assign_hull (m_polygon_points.begin (), m_polygon_points.end ());
              produce_polygon (poly, is_clear_polarity ());
            }

            m_current_gcode = -1;
            m_current_dcode = -1;

          } else if (gcode == 54) {

            // .. G54 - tool prepare
            m_current_gcode = -1;
            m_current_dcode = -1;

          } else if (gcode == 70) {

            // .. G70 - specify inches 
            set_unit (25400);

          } else if (gcode == 71) {

            // .. G71 - specify millimeters 
            set_unit (1000);

          } else if (gcode == 74) {

            // .. G74 - disable 360 degree circular interpolation
            m_360deg_circular = false;

          } else if (gcode == 75) {

            // .. G74 - enable 360 degree circular interpolation
            m_360deg_circular = true;

          } else if (gcode == 90) {

            // .. G90 - absolute mode
            m_relative = false;

          } else if (gcode == 91) {

            // .. G91 - relative mode
            m_relative = false;

          } else if (gcode == 0) {

            // .. G0 - move
            m_current_gcode = gcode;

          } else if (gcode == 2 ||
                     gcode == 3) {

            // .. G2, G3 - circular interpolation
            m_current_gcode = gcode;

          } else if (gcode == 1 ||
                     gcode == 10 ||
                     gcode == 11 ||
                     gcode == 12) {

            // TODO: Handle G10, G11, G12 correctly?
            // .. G1 - linear interpolation
            m_current_gcode = 1;

          } else if (gcode >= 0) {
            warn (tl::sprintf (tl::to_string (QObject::tr ("Invalid 'G' code %d - ignored")), gcode));
          }

        } else if (c == 'X') {

          double d = read_coord (ex);

          if (m_relative) {
            x += d;
          } else {
            x = d;
          }

          has_coord = true;

        } else if (c == 'Y') {

          double d = read_coord (ex);

          if (m_relative) {
            y += d;
          } else {
            y = d;
          }

          has_coord = true;

        } else if (c == 'I') {
          i = read_coord (ex);
        } else if (c == 'J') {
          j = read_coord (ex);
        } else if (c == 'D') {

          int dcode = -1;
          ex.read (dcode);

          if (dcode >= 10) {

            //  set current aperture
            if (dcode >= int (m_apertures.size ()) || m_apertures[dcode] == 0) {
              throw tl::Exception (tl::to_string (QObject::tr ("Aperture code D%d is invalid or undefined")), dcode);
            }

            m_current_aperture = m_apertures[dcode];

          } else if (dcode <= 3) {

            m_current_dcode = dcode;

            if (dcode == 3) {
              //  force a flash here even if there is no explicit coordinate 
              has_coord = true;
            }

          } else {
            warn (tl::sprintf (tl::to_string (QObject::tr ("Invalid D code %d ignored")), dcode));
          }

        } else {
          throw tl::Exception (tl::to_string (QObject::tr ("Invalid function code '%c'")), c);
        }

      }

      if (has_coord) {

        if (m_current_dcode == 2) {

          //  move
          if (m_polygon_mode) {

            //  D02 strokes close the polygon (and restart a new one)
            if (m_polygon_points.size () >= 3) {
              db::DPolygon poly;
              poly.assign_hull (m_polygon_points.begin (), m_polygon_points.end ());
              produce_polygon (poly, is_clear_polarity ());
            }

            m_polygon_points.clear ();

          }

        } else if (m_current_dcode == 3) {

          //  flash
          if (! m_current_aperture) {
            throw tl::Exception (tl::to_string (QObject::tr ("No aperture defined (missing G54 block)")));
          }

          if (m_polygon_mode) {
            warn (tl::to_string (QObject::tr ("D03 blocks are ignored in polygon mode")));
          } else {
            m_current_aperture->produce_flash (db::DCplxTrans (db::DVector (x, y)) * object_trans (), *this, ep (), is_clear_polarity ());
          }

        } else { // only if m_current_dcode == 1?

          //  move with "light" on
          if (m_current_gcode == 2 || m_current_gcode == 3) {

            // .. circular interpolation
            db::DPoint to = db::DPoint (x, y);
            db::DPoint from = db::DPoint (m_x, m_y);

            double rx = sqrt (i * i + j * j);
            double ry = (m_current_gcode == 3) ? rx : -rx;

            if (rx > 1e-12) {

              double a0 = 0.0, a1 = 0.0;
              db::DPoint center;

              bool has_center = false;
              double dmin = 0.0;

              if (! m_360deg_circular) {

                //  look for a good center point
                for (int v = 0; v < 4; ++v) {

                  db::DPoint c = from + db::DVector ((((v & 1) != 0) ? -i : i), (((v & 2) != 0) ? -j : j));

                  double aa0 = atan2 ((from.y () - c.y ()) / ry, (from.x () - c.x ()) / rx);
                  double aa1 = atan2 ((to.y () - c.y ()) / ry, (to.x () - c.x ()) / rx);

                  while (aa1 < aa0 - 1e-12) {
                    aa1 += M_PI * 2.0;
                  }

                  //  this is the single quadrant interpolation, so we can choose the one which is 
                  //  properly located for one quadrant.
                  if (aa1 - aa0 - 1e-6 < 0.5 * M_PI) {

                    double d = fabs (c.distance (to) - rx);
                    if (d < dmin || ! has_center) {
                      dmin = d;
                      center = c;
                      a0 = aa0;
                      a1 = aa1;
                      has_center = true;
                    }

                  }

                }

                if (! has_center) {
                  warn (tl::sprintf (tl::to_string (QObject::tr ("No suitable center point found for G%d code: P1=%s P2=%s I=%g J=%g")), 
                                     m_current_gcode, from.to_string (), to.to_string (), i, j));
                }

              } else {

                center = from + db::DVector (i, j);

                a0 = atan2 ((from.y () - center.y ()) / ry, (from.x () - center.x ()) / rx);
                a1 = atan2 ((to.y () - center.y ()) / ry, (to.x () - center.x ()) / rx);

                // multi quadrant interpolation
                while (a1 < a0 + 1e-12) {
                  a1 += M_PI * 2.0;
                }

                has_center = true;

              }

              if (has_center) {

                // TODO: 16 is an arbitrary choice (32 points/full circle)
                int n = int (ceil (fabs (a1 - a0) / (M_PI / 16.0) - 1e-4));
                double da = (a1 - a0) / n;

                for (int i = 0; i < n; ++i) {

                  double ae = a0 + (i + 1) * da;
                  db::DPoint pe = center + db::DVector (rx * cos (ae), ry * sin (ae));

                  if (m_polygon_mode) {
                    m_polygon_points.push_back (pe);
                  } else {

                    if (! m_current_aperture) {
                      throw tl::Exception (tl::to_string (QObject::tr ("No aperture defined (missing G54 block)")));
                    }

                    m_current_aperture->produce_linear (db::DCplxTrans (db::DVector (m_x, m_y)) * object_trans (), pe - db::DPoint (m_x, m_y), *this, ep (), is_clear_polarity ());

                  }

                  m_x = pe.x ();
                  m_y = pe.y ();

                }

              }

            }

          } else if (m_current_gcode == 0) {

            //  is it correct to ignore G00?
            warn (tl::to_string (QObject::tr ("Block with G00 interpolation mode is ignored")));

          } else if (m_current_gcode == 1 || m_current_gcode < 0) {

            // .. linear interpolation
            if (m_polygon_mode) {
              m_polygon_points.push_back (db::DPoint (x, y));
            } else {
              
              if (! m_current_aperture) {
                throw tl::Exception (tl::to_string (QObject::tr ("No aperture defined (missing G54 block)")));
              }

              m_current_aperture->produce_linear (db::DCplxTrans (db::DVector (m_x, m_y)) * object_trans (), db::DPoint (x, y) - db::DPoint (m_x, m_y), *this, ep (), is_clear_polarity ());

            }

          } else {
            throw tl::Exception (tl::to_string (QObject::tr ("G00 or unspecified 'G' code requires D03")));
          }

        }

        m_x = x;
        m_y = y;

      }

    }

  }

  if (! graphics_stack_empty ()) {
    throw tl::Exception (tl::to_string (QObject::tr ("AB block not closed")));
  }
}

void
RS274XReader::process_mcode (int /*mcode*/)
{
  //  no processing for M codes currently.
}

const std::string &
RS274XReader::get_block ()
{
  progress_checkpoint ();

  m_buffer.clear ();

  char c;
  while (! stream ().at_end () && (c = stream ().get_char ()) != '*') {
    m_buffer += c;
  }

  return m_buffer;
}

void
RS274XReader::read_as_parameter (const std::string &block)
{
  if (block == "AXBY") {
    m_axis_mapping = ab_xy;
  } else if (block == "AYBX") {
    m_axis_mapping = ab_yx;
  } else {
    throw tl::Exception (tl::to_string (QObject::tr ("Invalid argument '%s' for AS parameter")), block);
  }
}

void
RS274XReader::read_fs_parameter (const std::string &block)
{
  bool omit_lz = true;
  int ld = -1;
  int td = -1;

  tl::Extractor ex (block.c_str ());
  if (ex.test ("L")) {
    omit_lz = true;
  } else if (ex.test ("T")) {
    omit_lz = false;
  } else if (ex.test ("D")) {
    // TODO: clarify what to do in that case ..
  }

  if (ex.test ("A")) {
    m_relative = false;
  } else if (ex.test ("I")) {
    m_relative = true;
  }

  int i;
  if (ex.test ("N")) {
    ex.read (i);
  }
  if (ex.test ("G")) {
    ex.read (i);
  }

  ex.expect ("X");
  ex.read (i);
  ld = i / 10;
  td = i % 10;
  
  int j = i;
  ex.expect ("Y");
  ex.read (j);
  if (i != j) {
    throw tl::Exception (tl::to_string (QObject::tr ("X and Y format must be identical currently")));
  }

  if (ex.test ("D")) {
    ex.read (i);
  }
  if (ex.test ("M")) {
    ex.read (i);
  }

  ex.expect_end ();

  set_format (ld, td, omit_lz);
}

void
RS274XReader::read_mi_parameter (const std::string &block)
{
  tl::Extractor ex (block.c_str ());

  ex.expect ("A");
  int ma = 0;
  ex.read (ma);
  ex.expect ("B");
  int mb = 0;
  ex.read (mb);
  ex.expect_end ();

  bool mx = (ma != 0);
  bool my = (mb != 0);

  if (m_axis_mapping != ab_xy) {
    std::swap (mx, my);
  }

  update_local_mirror (mx, my);
}

void
RS274XReader::read_mo_parameter (const std::string &block)
{
  if (block == "IN") {
    set_unit (25400);
  } else if (block == "MM") {
    set_unit (1000);
  } else {
    throw tl::Exception (tl::to_string (QObject::tr ("Invalid argument of M0 parameter - must be 'IN' or 'MM', not '%s'")), block);
  }
}

void
RS274XReader::read_of_parameter (const std::string &block)
{
  // TODO: relationship to IO paramter???
  tl::Extractor ex (block.c_str ());

  ex.expect ("A");
  double ao = 0.0;
  ex.read (ao);
  ao *= unit ();
  ex.expect ("B");
  double bo = 0.0;
  ex.read (bo);
  bo *= unit ();
  ex.expect_end ();

  double ox = ao;
  double oy = bo;

  if (m_axis_mapping != ab_xy) {
    std::swap (ox, oy);
  }

  update_local_offset (ox, oy);
}

void
RS274XReader::read_sf_parameter (const std::string &block)
{
  tl::Extractor ex (block.c_str ());

  ex.expect ("A");
  double sx = 1.0;
  ex.read (sx);
  ex.expect ("B");
  double sy = 1.0;
  ex.read (sy);
  ex.expect_end ();

  if (m_axis_mapping != ab_xy) {
    std::swap (sx, sy);
  }

  if (fabs (sx - sy) > 1e-6) {
    throw tl::Exception (tl::to_string (QObject::tr ("Different scalings for x and y axis is not supported currently.")));
  }

  update_local_scale (sx);
}

void
RS274XReader::read_ls_parameter (const std::string &block)
{
  tl::Extractor ex (block.c_str ());

  double s = 1.0;
  ex.read (s);

  update_object_scale (s);
}

void
RS274XReader::read_lr_parameter (const std::string &block)
{
  tl::Extractor ex (block.c_str ());

  double a = 0.0;
  ex.read (a);

  update_object_angle (a);
}

void
RS274XReader::read_lm_parameter (const std::string &block)
{
  tl::Extractor ex (block.c_str ());

  bool omx = false, omy = false;
  while (! ex.at_end ()) {
    if (ex.test ("X")) {
      omy = true;  //  "my == mirror at y axis" is "X == mirror along x axis"
    } else if (ex.test ("Y")) {
      omx = true;  //  "mx == mirror at x axis" is "Y == mirror along y axis"
    } else {
      break;
    }
  }

  update_object_mirror (omx, omy);
}

void
RS274XReader::read_ij_parameter (const std::string & /*block*/)
{
  warn (tl::to_string (QObject::tr ("IJ parameters are ignored currently")));
}

void
RS274XReader::read_in_parameter (const std::string & /*block*/)
{
  // image name ignored currently
}

void
RS274XReader::read_io_parameter (const std::string &block)
{
  // TODO: clarify: relationship to OF paramter???
  tl::Extractor ex (block.c_str ());

  ex.expect ("A");
  double ao = 0.0;
  ex.read (ao);
  ao *= unit ();
  ex.expect ("B");
  double bo = 0.0;
  ex.read (bo);
  bo *= unit ();
  ex.expect_end ();

  double ox = ao;
  double oy = bo;

  if (m_axis_mapping != ab_xy) {
    std::swap (ox, oy);
  }

  update_local_offset (ox, oy);
}

void
RS274XReader::read_ip_parameter (const std::string &block)
{
  tl::Extractor ex (block.c_str ());

  if (ex.test ("POS")) {
    set_inverse (false);
  } else if (ex.test ("NEG")) {
    set_inverse (true);
  } 

  ex.expect_end ();
}

void
RS274XReader::read_ir_parameter (const std::string &block)
{
  tl::Extractor ex (block.c_str ());

  double rot = 0.0;
  ex.read (rot);
  update_local_angle (rot);
}

void
RS274XReader::read_pf_parameter (const std::string & /*block*/)
{
  warn (tl::to_string (QObject::tr ("PF parameters are ignored")));
}

void
RS274XReader::read_ad_parameter (const std::string &block)
{
  tl::Extractor ex (block.c_str ());

  if (ex.at_end ()) {
    //  ignore "%AD*" commands ..
    return;
  }

  ex.expect ("D");
  int dcode = 0;
  ex.read (dcode);

  if (dcode < 0) {
    throw tl::Exception (tl::to_string (QObject::tr ("Invalid D code for AD parameter")));
  }

  while (int (m_apertures.size ()) <= dcode) {
    m_apertures.push_back (0);
  }

  std::string name;
  while (*ex && *ex != '*' && *ex != ',') {
    name += *ex;
    ++ex;
  }

  if (name == "C") {
    m_apertures[dcode] = new RS274XCircleAperture (*this, ex);
  } else if (name == "R") {
    m_apertures[dcode] = new RS274XRectAperture (*this, ex);
  } else if (name == "O") {
    m_apertures[dcode] = new RS274XOvalAperture (*this, ex);
  } else if (name == "P") {
    m_apertures[dcode] = new RS274XRegularAperture (*this, ex);
  } else if (m_aperture_macros.find (name) != m_aperture_macros.end ()) {
    m_apertures[dcode] = new RS274XMacroAperture (*this, name, m_aperture_macros[name], ex);
  } else {
    throw tl::Exception (tl::to_string (QObject::tr ("Invalid aperture name '%s' (not a macro name and not a standard aperture) for AD parameter")), name);
  }
}

void
RS274XReader::install_block_aperture (const std::string &d, const db::Region &region)
{
  int dcode = 0;

  try {
    tl::Extractor ex (d.c_str ());
    ex.expect ("D");
    ex.read (dcode);
    ex.expect_end ();
  } catch (...) {
    throw tl::Exception (tl::to_string (QObject::tr ("Invalid aperture code string for AB command")));
  }

  if (dcode < 0) {
    throw tl::Exception (tl::to_string (QObject::tr ("Invalid D code for AB command")));
  }

  while (int (m_apertures.size ()) <= dcode) {
    m_apertures.push_back (0);
  }

  m_apertures[dcode] = new RS274XRegionAperture (region);
}

void
RS274XReader::read_am_parameter (const std::string &block)
{
  tl::Extractor ex (block.c_str ());

  std::string name;
  while (*ex && *ex != '*') {
    name += *ex;
    ++ex;
  }

  ex.expect ("*");

  m_aperture_macros.insert (std::make_pair (name, std::string (ex.skip ())));
}

void
RS274XReader::read_ko_parameter (const std::string & /*block*/)
{
  warn (tl::to_string (QObject::tr ("KO parameters are not supported currently")));
}

void
RS274XReader::read_ln_parameter (const std::string & /*block*/)
{
  // TODO: implement layer name
}

void
RS274XReader::read_lp_parameter (const std::string &block)
{
  if (block == "C") {
    //  when we encounter the first LP parameter, and it is a clear layer, we
    //  guess negative polarity (as do some viewers)
    if (m_guess_polarity) {
      m_neg_polarity = true;
      m_guess_polarity = false;
    }
    m_clear = true;
  } else if (block == "D") {
    if (m_guess_polarity) {
      m_neg_polarity = false;
      m_guess_polarity = false;
    }
    m_clear = false;
  } else {
    throw tl::Exception (tl::to_string (QObject::tr ("Invalid argument '%s' for LP parameter")), block);
  }
}

void
RS274XReader::read_sr_parameter (const std::string &block)
{
  reset_step_and_repeat ();

  tl::Extractor ex (block.c_str ());

  if (ex.at_end ()) {
    //  empty %SR* comment: just reset
    return;
  }

  int nx = 1, ny = 1;
  double dx = 0.0, dy = 0.0;

  while (! ex.at_end ()) {
    if (ex.test ("X")) {
      ex.read (nx);
    } else if (ex.test ("Y")) {
      ex.read (ny);
    } else if (ex.test ("I")) {
      ex.read (dx);
    } else if (ex.test ("J")) {
      ex.read (dy);
    } else {
      break;
    }
  }
  ex.expect_end ();

  if (nx > 1 || ny > 1) {

    dx *= unit ();
    dy *= unit ();

    std::vector <db::DVector> steps;
    steps.reserve (nx * ny);
    for (int i = 0; i < nx; ++i) {
      for (int j = 0; j < ny; ++j) {
        steps.push_back (db::DVector (i * dx, j * dy));
      }
    }

    step_and_repeat (steps);

  }
}

void
RS274XReader::read_if_parameter (const std::string & /*block*/)
{
  warn (tl::to_string (QObject::tr ("IF parameters are not supported currently")));
}

}

