package nxm.sys.libg;

import java.awt.Color;
import java.awt.Font;
import java.util.ArrayList;
import java.util.List;

import nxm.sys.inc.Drawable;
import nxm.sys.inc.InternalUseOnly;
import nxm.sys.lib.Midas;

/**
 *  This class provides the means of grouping 3D points based on LatLon map zoom level.
 *  The LatLon map is divided into latitude longitude regions represented by RegionGroups.
 *  Each RegionGroup holds an 8x8 grid of Regions, where an individual Region is the
 *  smallest "bin" used for sorting points into latitude longitude regions.<br>
 *  This Regioning logic allows either the rendering of groups of points as "Regions"
 *  or the easy determination of how many points are in a given latitude longitude region.<br>
 *  <br>
 *  <pre>
 *  -180                                                                               180
 *  ***************************************************************************************  90
 *  *               *               *               *               *     *               *
 *  *               *               *               *               *     *               *
 *  *               *               *               *               *     *               *
 *  *     Region    *     Region    *     Region    *     Region    * ... *     Region    *
 *  *     Group     *     Group     *     Group     *     Group     *     *     Group     *
 *  *               *               *               *               *     *               *
 *  *               *               *               *               *     *               *
 *  *               *               *               *               *     *               *
 *  ***************************************************************************************
 *  *r|r|r|r|r|r|r|r*               *               *               *     *               *
 *  *r|r|r|r|r|r|r|r*               *               *               *     *               *
 *  *r|r|r|r|r|r|r|r*               *               *               *     *               *
 *  *r|r|r|r|r|r|r|r*     Region    *     Region    *     Region    * ... *     Region    *
 *  *r|r|r|r|r|r|r|r*     Group     *     Group     *     Group     *     *     Group     *
 *  *r|r|r|r|r|r|r|r*               *               *               *     *               *
 *  *r|r|r|r|r|r|r|r*               *               *               *     *               *
 *  *r|r|r|r|r|r|r|r*               *               *               *     *               *
 *  ***************************************************************************************
 *  *       .       *       .       *       .       *       .       *     *       .       *
 *  *       .       *       .       *       .       *       .       *     *       .       *
 *  *       .       *       .       *       .       *       .       *     *       .       *
 *  ***************************************************************************************
 *  *               *               *               *               *     *               *
 *  *               *               *               *               *     *               *
 *  *               *               *               *               *     *               *
 *  *     Region    *     Region    *     Region    *     Region    * ... *     Region    *
 *  *     Group     *     Group     *     Group     *     Group     *     *     Group     *
 *  *               *               *               *               *     *               *
 *  *               *               *               *               *     *               *
 *  *               *               *               *               *     *               *
 *  *************************************************************************************** -90
 *
 *  where 'r' is an individual Region  within a Region Group
 *  </pre>
 *  When rendering clusters on a map and there is no zooming, the Cluster drawn represents
 *  the number of points in a whole Region group. The first level (defined by percent) of zooming
 *  renders clusters based on the number of points in a 4x4 region of a Region Group.  The
 *  next level of zooming counts points in a 2x2 region of a Region Group.  The next level of
 *  zooming counts points in an individual Region .  At a certain level, points are always rendered
 *  as points and never as clusters.
 *
 *  @see Region
 *  @author Kevin Esterheld /Jeanette McCall
 *  @since NeXtMidas 3.9.4 for NXM-3542
 */
public class RegionGroup {
  static final int    LON_GRID_SIZE       = 20;
  static final int    LAT_GRID_SIZE       = 20;
  static final int    REGION_GRID_COUNT  = 8;
  static final double MAX_ZOOM_PERCENT    = 0.005;
  static final int    MAX_ZOOM_LEVEL      = 5;
  static final int    THIRD_ZOOM_LEVEL    = 1;
  static final int    SECOND_ZOOM_LEVEL   = 2;
  static final int    FIRST_ZOOM_LEVEL    = 4;
  static final int    MAX_ZOOM_FACTOR     = 1;
  static final int    THIRD_ZOOM_FACTOR   = 8;
  static final int    SECOND_ZOOM_FACTOR  = 4;
  static final int    FIRST_ZOOM_FACTOR   = 2;
  static final int    ZOOM_PERCENT_FIRST  = 3;
  static final int    ZOOM_PERCENT_SECOND = 10;
  static final int    ZOOM_PERCENT_THIRD  = 20;
  static final int    ZOOM_PERCENT_MAX    = 75;

  private static final double Y_SHIFT_FACTOR = .1;
  private static final Color AQUABLUE = new Color(0,255,255, 125); // aqua blue
  private static final Color YELLOW = new Color(255, 255, 0, 125); // yello
  private static final Color RED = new Color(255, 0, 0, 125); // red
  private static final Color PINK = new Color(255, 192, 203, 125);

  Point3D topRight;
  Point3D topLeft;
  Region[][] regions = new Region[REGION_GRID_COUNT][REGION_GRID_COUNT];
  double x1;
  double y1;
  double xRange = Double.valueOf(LON_GRID_SIZE)/Double.valueOf(REGION_GRID_COUNT);
  double yRange = Double.valueOf(LAT_GRID_SIZE)/Double.valueOf(REGION_GRID_COUNT);
  double minX = Double.MAX_VALUE;
  double maxX = -Double.MAX_VALUE;
  double minY = Double.MAX_VALUE;
  double maxY = -Double.MAX_VALUE;
  Point3D center;
  MPlot MP;
  private LayerT5 layer;

  /** Region Group constructor
   *
   *  @param indexOfGroupX  array x index (0 based) of this Region Group
   *  @param indexOfGroupY  array y index (0 based) of this Region Group
   *  @param layer          LayerT5 this Region Group is a part of
   */
  public RegionGroup(int indexOfGroupX, int indexOfGroupY, LayerT5 layer ) {
    this.layer = layer;
    double xVal = (indexOfGroupX * LON_GRID_SIZE) + MPlot.LONGITUDE_DEFAULT_MIN;
    double yVal = (indexOfGroupY * LAT_GRID_SIZE) + MPlot.LATITUDE_DEFAULT_MIN;
    // add the lat grid size to the lat val to make the box go up - opposite from how longitude works. left to right means the smaller value is the left side. down to up means the smaller value is the bottom of the grid.
    topLeft = new Point3D(layer);
    topLeft.setPos(0,yVal+LAT_GRID_SIZE,xVal);
    topLeft.prep(false);
    topRight = new Point3D(layer);
    topRight.setPos(0,yVal+LAT_GRID_SIZE, xVal + LON_GRID_SIZE);
    topRight.prep(false);
    center = new Point3D(layer);
    x1 = xVal;
    y1 = yVal;
    this.MP = layer.getMPlot();
  }

  /** Add a point to appropriate Region within the Region Group
   *
   *  @param point Point3D to add
   *  @return index at which the point was placed within its Region
   */
  public int addPoint (Point3D point) {

    int[] indexes = getIndexes(point);
    int xIndex = indexes[0];
    int yIndex = indexes[1];
    if (point.getPosLon()>maxX) {
      maxX = point.getPosLon();
    }
    if (point.getPosLon()<minX) {
      minX = point.getPosLon();
    }
    if (point.getPosLat()>maxY) {
      maxY = point.getPosLat();
    }
    if (point.getPosLat()<minY) {
      minY = point.getPosLat();
    }
    double centerX = (maxX + minX)/2;
    double centerY = (maxY + minY)/2;
    center.setPos(0,centerY,centerX);
    if (regions[xIndex][yIndex]== null) regions[xIndex][yIndex] = new Region();
    return regions[xIndex][yIndex].addPoint(point);

  }

  /** Get the current number of points in this Region Group
   *
   *  @return current number of points in this Region Group
   */
  public int getNumPtsInRegionGroup () {
    int size = 0;
    for (int i = 0; i<REGION_GRID_COUNT;i++) {
      for (int j = 0; j < REGION_GRID_COUNT; j++) {
        if (regions[i][j]!=null) size += regions[i][j].getNumPtsInRegion();
      }
    }
    return size;
  }

  private int[] getIndexes(Point3D point) {
    double x = point.getPosLon();
    double y = point.getPosLat();
    int xIndex = 1;
    int yIndex = 1;
    while (xIndex <= REGION_GRID_COUNT) {
      double maxXVal = x1 + xIndex * xRange;
      if (x < maxXVal) {
        xIndex--;
        break;
      }
      xIndex++;
    }

    while (yIndex <= REGION_GRID_COUNT) {
      double maxYVal = y1 + yIndex * yRange;
      if (y < maxYVal) {
        yIndex--;
        break;
      }
      yIndex++;
    }
    yIndex = REGION_GRID_COUNT - yIndex - 1; // reverse the index location - the larger the index the lower in latitude the point is.
    xIndex = Math.min(xIndex, REGION_GRID_COUNT - 1); // account for larger than bounds x
    yIndex = Math.max(yIndex, 0); // account for larger than bounds y (reversed to be negative)
    return new int[] {xIndex,yIndex};
  }

  /** Remove a point from this Region Group by removing it
   *  from its specific Region
   *
   *  @param point point to remove
   */
  public void removePoint (Point3D point) {
    int[] indexes = getIndexes(point);
    int indexX = indexes[0];
    int indexY = indexes[1];

    Region region = regions[indexX][indexY];
    if (region==null) return;
    region.removePoint(point.indexInRegion);
  }

  // may not be needed
  private int[] getRegionPointCountByZoomlevel (int zoomLevel) {
    int zoomFactor = MAX_ZOOM_FACTOR;
    int regionPointCount = 1;

    if (zoomLevel==THIRD_ZOOM_LEVEL) {
      regionPointCount = 16;
      zoomFactor=THIRD_ZOOM_FACTOR;
    }
    else if (zoomLevel==SECOND_ZOOM_LEVEL) {
      regionPointCount = 8;
      zoomFactor=SECOND_ZOOM_FACTOR;
    }
    else if (zoomLevel==FIRST_ZOOM_LEVEL) {
      regionPointCount = 4;
      zoomFactor=FIRST_ZOOM_FACTOR;
    }
    int[] sizes = new int[regionPointCount];


    double xRange = Double.valueOf(LON_GRID_SIZE)/zoomFactor;
    double yRange = Double.valueOf(LAT_GRID_SIZE)/zoomFactor;
    for(int i = 0; i < regions.length; i+= zoomLevel) {
      for (int j = 0; j < regions[i].length; j+=zoomLevel) {
        MP.gc.setFont(layer.getNumberFontSmall());
        double x = topLeft.posLon + (i/zoomLevel)*xRange;
        double y = topLeft.posLat - (j/zoomLevel)*yRange;
        Point3D topLeftTemp = new Point3D(layer);
        topLeftTemp.setPos(0,y,x);
        topLeftTemp.prep(false);
        Point3D topRightTemp = new Point3D(layer);
        topRightTemp.setPos(0,y,x+xRange);
        List<Point3D> pointsToDraw = new ArrayList<>();
        int size = 0;
        // Not really nested - the outer loop skips the indexes we are going over in here - this is done for grouping of regions
        for (int xIndex = i; xIndex<(i+zoomLevel);xIndex++) {
          for (int yIndex = j; yIndex < (j+zoomLevel); yIndex++) {
            if (regions[xIndex][yIndex]!=null) {
              Region region = regions[xIndex][yIndex];
              for (int k = 0; k<region.getNumPointsInRegion();k++) {
                Point3D pointToDraw = region.points[k];
                if (pointToDraw!=null) {
                  if (pointsToDraw.size()<=layer.minimumClusterSize) pointsToDraw.add(pointToDraw);
                  size++;
                }
              }
            }
          }
        }
        sizes[i/zoomLevel]=size;
      }
    }
    return sizes;
  }

  /** This will draw the cluster. There are 3 ways it will draw.
   *  1 - It will draw all the points if the region has less than
   *      the {@link LayerT5#minimumClusterSize} which is defaulted to 5.
   *  2 - it will draw as one cluster for the whole region grouping if zoomed out
   *  3 - It will draw individual clusters for each semi region group based on the same logic as number 1
   * @param zoomLevel 1-3, anything greater should draw just points.
   * @param lin          The line that holds the attributes for the circle/point
   * @param regionGroups List of region groupings groups to be drawn
   * @param xIndex       The xIndex into the region group
   * @param yIndex       The yIndex into the region group
   * @param MP           The MPlot instance
   */
  static void drawRegionPointGroupings(int zoomLevel, Line lin, List<RegionGroup> regionGroups, int xIndex, int yIndex, MPlot MP) {
    Pix pix = new Pix(2,0);
    if (regionGroups.size()==0)return;
    Point3D topLeft = regionGroups.get(0).topLeft;
    Point3D topRight = regionGroups.get(0).topRight;
    LayerT5 layer = regionGroups.get(0).layer;
    MP.viewer.rwc2pix(new double[] {topLeft.getPosLon(),topLeft.getPosLat(),topRight.getPosLon(),topRight.getPosLat()}, 0, 2, 2, pix);
    if ((pix.x[0]<MP.ix1 && pix.x[1]<MP.ix1) ||
        pix.x[0]>MP.ix2 && pix.x[1]>MP.ix2) return;
    int diameter=((Math.abs(pix.x[0] - pix.x[1])));

    int regionPointCount = 0;
    for (RegionGroup cg : regionGroups) {
      regionPointCount += cg.getNumPtsInRegionGroup();
    }

    topLeft.prep(false);
    topRight.prep(false);
    // draw all the points if there are not enough points or the maxzoomlevel is hit
    if (regionPointCount <= layer.minimumClusterSize) {
      drawPointsNonGrouped(lin, regionGroups);
      return;
    }
    // Draw high level cluster without breaking into semi region clusters
    if (zoomLevel == 0 ) {
      drawCluster(diameter, regionPointCount, topLeft._ixPos, topLeft._iyPos, MP, layer);
    }
    // Break up the region into semi region groupings
    else {
      drawSemiClusters (lin, zoomLevel, diameter, regionGroups,MP,layer,topLeft);
    }
  }

  /** Draw the circle with the given parameters
   *
   *  @param diameter - width of the circle
   *  @param size - count of points in the circle
   *  @param ixPos - the x pixel location (left side of circle)
   *  @param iyPos - the y pixel location (top of the circle)
   *  @param MP     - MPlot instance
   *  @param layer  - LayerT5 instance
   */
  static void drawCluster (int diameter, int size, int ixPos, int iyPos, MPlot MP, LayerT5 layer) {
    Color currentColor = MP.gc.getColor();
    MP.gc.setColor(getRegionCircleColor(size));
    int numShift = setFontAndGetNumShift(diameter, size, MP, layer);
    int yShift = (int) (diameter*Y_SHIFT_FACTOR);
    MP.gc.fillOval(ixPos, iyPos,diameter,diameter);
    MP.gc.setColor(Color.BLACK);
    MP.gc.drawString(size+"", ixPos + numShift,iyPos + diameter/2 + yShift);
    MP.gc.setColor(currentColor); // reset the color
  }

  /** This will draw all the points in a region
   *
   *  @param lin The line for the point graphical settings
   *  @param regionGroups The list of RegionGroup to draw the points from.
   */
  static void drawPointsNonGrouped (Line lin, List<RegionGroup> regionGroups) {
    for (int i = 0; i<REGION_GRID_COUNT;i++) {
      for (int j = 0; j < REGION_GRID_COUNT; j++) {
        for (RegionGroup cg : regionGroups) {
          if(cg.regions[i][j]!=null) cg.regions[i][j].drawAllPoints(lin);;
        }
      }
    }
  }

  /** Called by drawRegionPointGroupings when zoomed in at all.
   *  This will split a region in to more regions based on zoom level.
   *
   *  @param lin             The line that holds the attributes for the circle/point
   *  @param zoomLevel       1-3, anything greater should draw just points.
   *  @param diameter        The width of the circle to be drawn
   *  @param regionGroups    List of RegionGroup objects that hold the regions
   *  @param MP              MPlot instance
   *  @param layer           LayerT5 instance
   *  @param topLeft         Point3D object that is located at the top left of the region
   */
  private static void drawSemiClusters (Line lin, int zoomLevel, int diameter, List<RegionGroup> regionGroups, MPlot MP, LayerT5 layer, Point3D topLeft) {
    int zoomFactor = MAX_ZOOM_FACTOR;
    if      (zoomLevel==THIRD_ZOOM_LEVEL) zoomFactor=THIRD_ZOOM_FACTOR;
    else if (zoomLevel==SECOND_ZOOM_LEVEL) zoomFactor=SECOND_ZOOM_FACTOR;
    else if (zoomLevel==FIRST_ZOOM_LEVEL) zoomFactor=FIRST_ZOOM_FACTOR;
    diameter /= zoomFactor;
    double gridRange = Double.valueOf(LON_GRID_SIZE)/zoomFactor;
    for(int i = 0; i < REGION_GRID_COUNT; i+= zoomLevel) {
      for (int j = 0; j < REGION_GRID_COUNT; j+=zoomLevel) {
        drawSemiClusters(lin, zoomLevel, i, j, diameter, gridRange, MP, layer, topLeft, regionGroups);
      }
    }
  }

  /** Draw a semi region grouping by sending in the dimension, start x index and start y index
   *
   *  @param lin              Line object holds the graphical options
   *  @param regionDimension  size of the dimension - ex: 1x1,2x2,4x4 so the value is 1,2,or 4
   *  @param xIndexInRegion   The start x index in the region
   *  @param yIndexInRegion   The start y index in the region
   *  @param diameter         The diameter of the circle
   *  @param gridRange        The size of the grid
   *  @param MP               MPlot instance
   *  @param layer            The LayerT5 instance
   *  @param topLeft          LayerT5 instance
   *  @param regionGroups    List of RegionGroup objects that hold the Regions
   */
  static void drawSemiClusters (Line lin, int regionDimension, int xIndexInRegion, int yIndexInRegion, int diameter, double gridRange,
                                       MPlot MP, LayerT5 layer, Point3D topLeft, List<RegionGroup> regionGroups) {
    List<Region> regionsInSemiRegionGrouping = new ArrayList<>();
    double x = topLeft.posLon + (xIndexInRegion/regionDimension)*gridRange;
    double y = topLeft.posLat - (yIndexInRegion/regionDimension)*gridRange;
    Point3D topLeftTemp = new Point3D(layer);
    topLeftTemp.setPos(0,y,x);
    topLeftTemp.prep(false);
    Point3D topRightTemp = new Point3D(layer);
    topRightTemp.setPos(0,y,x+gridRange);
    int size = getSizeOfSemiRegionGrouping(regionDimension, xIndexInRegion, yIndexInRegion, regionsInSemiRegionGrouping, regionGroups);
    // Skip this one, there are no points
    if (size==0) return;
    // Not enough points to draw region grouping, draw all the points of this semi region grouping
    if (size<=layer.minimumClusterSize) {
      drawAllPointsInRegions(regionsInSemiRegionGrouping, lin);
      return;
    }
    // draw the region grouping circle
    drawCluster(diameter, size, topLeftTemp._ixPos, topLeftTemp._iyPos, MP, layer);
  }

  /** Get the point count within the semi region grouping
   *
   * @param regionDimension        size of the dimension ex 1x1,2x2,4x4 so the value is 1,2,or 4
   * @param xIndexInRegion          xIndex into the regions
   * @param yIndexInRegion          yIndex into the regions
   * @param regionsInSemiRegionGrouping   List of RegionGroup objects that hold the regions
   * @param regionGroups            List of RegionGroup objects that hold the regions
   * @return The point count across the regions in a given semi region grouping
   */
  static int getSizeOfSemiRegionGrouping(int regionDimension, int xIndexInRegion, int yIndexInRegion,
                                         List<Region> regionsInSemiRegionGrouping, List<RegionGroup> regionGroups) {
    int size = 0;
    // Not really nested - the outer loop skips the indexes we are going over in here - this is done for grouping of regions
    int endXIndex = xIndexInRegion+regionDimension;
    int endYIndex = yIndexInRegion+regionDimension;
    for (int xIndexInSemiRegion = xIndexInRegion; xIndexInSemiRegion < endXIndex; xIndexInSemiRegion++) {
      for (int yIndexInSemiRegion = yIndexInRegion; yIndexInSemiRegion < endYIndex; yIndexInSemiRegion++) {
        for (RegionGroup regionGroup : regionGroups) {
          Region region = regionGroup.regions[xIndexInSemiRegion][yIndexInSemiRegion];
          if (region!=null) {
            regionsInSemiRegionGrouping.add(region);
            size += region.getNumPtsInRegion();
          }
        }
      }
    }
    return size;
  }

  /** Draws all points from given list of regions
   *
   * @param regionsInSemiRegion List of regions
   * @param lin                   Line object containing the graphical parameters
   */
  static void drawAllPointsInRegions (List<Region> regionsInSemiRegion, Line lin) {
    for(Region region : regionsInSemiRegion) region.drawAllPoints(lin);
  }

  /** Get the region circle color based on point count
   *
   *  @param size the amount of points within the region
   *  @return the color to make the region circle
   */
  private static Color getRegionCircleColor (int size) {
    if (size<10) return AQUABLUE;
    if (size<100) return YELLOW;
    if (size<1000) return RED;
    if (size<10000) return PINK;
    return new Color(127,0,255);
  }

  /** Count the points in a given range
   *  <br><b>Note:</b> The coordinates have to be in the right order x1 &lt; x2 and y1 &lt; y2
   *  @param x1 first x value (-180 to 180)
   *  @param x2 second x value (-180 to 180)
   *  @param y1 first y value (-90 to 90)
   *  @param y2 second y value (-90 to 90)
   *  @return the point count in the given range
   *  @since NeXtMidas 3.9.3
   */
  int countPointsInRange (double x1, double x2, double y1, double y2) {
    int pointCount = 0;
    for (int i = 0; i< regions.length; i++) {
      for (int j = 0; j < regions[i].length; j++) {
        Region region = regions[i][j];
        if (region==null) continue;
        pointCount += region.countPointsInRange(x1, x2, y1, y2);
      }
    }
    return pointCount;
  }


  static int getXIndex (double val) {
    val+=180;
    int index = (int) (val/RegionGroup.LON_GRID_SIZE);
    index = Math.min(Math.max(index, 0), (MPlot.LONGITUDE_RANGE/RegionGroup.LON_GRID_SIZE) -1); // keep in range

    return index;
  }

  static int getYIndex (double val) {
    val+=90;
    int index = (int) (val/RegionGroup.LAT_GRID_SIZE);
    index = Math.min(Math.max(index, 0), (MPlot.LATITUDE_RANGE/RegionGroup.LAT_GRID_SIZE) -1); // keep in range
    return index;
  }

  /** Sets the font size for each level (small, medium, large)
   *  Calculates how much to shift number from center.
   *
   *  @param diameter - the diameter of the circle to determine the shift and the font size.
   *  @param size     - the point count to determine the shift -
   *                    When there is more than 100, there are 3 digits, 1000 4 digits
   *  @return           the shift to be applied to the number location
   */
  static int setFontAndGetNumShift (int diameter, int size, MPlot MP, LayerT5 layer) {
    int numShift = (int) (diameter*.45);
    if (size<100) {
      MP.gc.setFont(layer.getNumberFontSmall());
    }
    else if(size<1000) {
      MP.gc.setFont(layer.getNumberFontMedium());
      numShift = (int) (diameter*.3);
    }
    else {
      MP.gc.setFont(layer.getNumberFontLarge());
      numShift = (int) (diameter*.2);
    }
    return numShift;
  }

  static int getZoomLevel (double zoom) {
    zoom*=100;
    int zoomLevel = 0;
    if      (zoom < RegionGroup.ZOOM_PERCENT_FIRST)  zoomLevel = RegionGroup.MAX_ZOOM_LEVEL; // all points
    else if (zoom < RegionGroup.ZOOM_PERCENT_SECOND)  zoomLevel = RegionGroup.THIRD_ZOOM_LEVEL; // 64 regions or 1x1
    else if (zoom < RegionGroup.ZOOM_PERCENT_THIRD) zoomLevel = RegionGroup.SECOND_ZOOM_LEVEL; // 2x2
    else if (zoom < RegionGroup.ZOOM_PERCENT_MAX) zoomLevel = RegionGroup.FIRST_ZOOM_LEVEL; // 4x4
    return zoomLevel;
  }

  static void drawGrouping (Line lin, int zoomLevel, List<LayerT5> layers, MPlot MP) {
    if (MP==null) return; // Nothing to draw
    if (zoomLevel>=MAX_ZOOM_LEVEL) {
      Midas.log("Warning, drawing region grouping when zoomed in to far, running full draw on all LayerT5s", Midas.WARNING);
      for (LayerT5 layer : layers) {
        layer.draw(Drawable.FULL);
      }
      return;
    }
    // save off current graphical settings
    Color currentColor = MP.gc.getColor();
    Font currentFont = MP.gc.getFont();
    // Find the visible indexes so not to draw groupings off screen.
    int numX = MPlot.LONGITUDE_RANGE / RegionGroup.LON_GRID_SIZE;
    int xMinIndex = Math.max(RegionGroup.getXIndex(MP.rx1), 0) % numX; // keep in range with LATLONCONTINUOUS
    int yMinIndex = Math.max(RegionGroup.getYIndex(MP.ry1), 0); // can be less than 0
    int yMaxIndex = Math.min(RegionGroup.getYIndex(MP.ry2), (MPlot.LATITUDE_RANGE/RegionGroup.LAT_GRID_SIZE) -1); // can not be greater than 9
    for (int i = xMinIndex, k = 0; k < numX; k++) {
      drawXRegionPointGroupings (lin, zoomLevel, layers, MP,
          i, yMinIndex, yMaxIndex, currentColor, currentFont);
      i = (i + 1) % numX;
    }
  }

  private static void drawXRegionPointGroupings (Line lin, int zoomLevel, List<LayerT5> layers, MPlot MP,
                                                 int i, int yMinIndex, int yMaxIndex, Color currentColor, Font currentFont) {
    for (int j = yMinIndex; j <= yMaxIndex; j++) {
      List<RegionGroup> regionGroups = new ArrayList<>();
      for (LayerT5 t5 : layers) {
        RegionGroup regionGroup = t5.regionGroups[i][j];
        if (regionGroup!=null) regionGroups.add(regionGroup);
      }
      RegionGroup.drawRegionPointGroupings(zoomLevel, lin, regionGroups, i,  j, MP);
      // reset graphical settings after each draw
      MP.gc.setColor(currentColor);
      MP.gc.setFont(currentFont);
    }
  }

}
