// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.

package com.intellij.uiDesigner.designSurface;

import com.intellij.openapi.wm.ex.WindowManagerEx;
import com.intellij.uiDesigner.core.GridConstraints;
import com.intellij.uiDesigner.radComponents.RadComponent;
import com.intellij.uiDesigner.radComponents.RadContainer;

import java.awt.*;
import java.lang.ref.SoftReference;

import static com.intellij.reference.SoftReference.dereference;


public final class CachedGridImage {
  private static final Object CACHED_GRID_IMAGE_KEY = new Object();
  private static final float[] ourDashes = new float[] { 3.0f, 1.0f };

  private final Image myImage;
  private int[] myHorzGridLines;
  private int[] myVertGridLines;
  private int[] myRows;
  private int[] myRowSpans;
  private int[] myCols;
  private int[] myColSpans;

  private CachedGridImage(final RadContainer container) {
    final GraphicsConfiguration graphicsConfiguration =
      WindowManagerEx.getInstanceEx().getFrame(container.getProject()).getGraphicsConfiguration();
    if (container.getWidth() * container.getHeight() < 4096*4096) {
      myImage = graphicsConfiguration.createCompatibleImage(container.getWidth(), container.getHeight(),
                                                          Transparency.BITMASK);
      update(container);
    }
    else {
      // create fake image for insanely large containers
      myImage = graphicsConfiguration.createCompatibleImage(16, 16,  Transparency.BITMASK);
    }
  }

  private void update(final RadContainer container) {
    if (container.getWidth() * container.getHeight() >= 4096*4096) return;
    int count = container.getComponentCount();
    int[] rows = new int[count];
    int[] rowSpans = new int[count];
    int[] cols = new int[count];
    int[] colSpans = new int[count];
    for(int i=0; i<count; i++) {
      GridConstraints c = container.getComponent(i).getConstraints();
      rows [i] = c.getRow();
      rowSpans [i] = c.getRowSpan();
      cols [i] = c.getColumn();
      colSpans [i] = c.getColSpan();
    }
    int[] horzGridLines = container.getGridLayoutManager().getHorizontalGridLines(container);
    int[] vertGridLines = container.getGridLayoutManager().getVerticalGridLines(container);
    if (!arraysEqual(horzGridLines, myHorzGridLines) ||
        !arraysEqual(vertGridLines, myVertGridLines) ||
        !arraysEqual(rows, myRows) ||
        !arraysEqual(rowSpans, myRowSpans) ||
        !arraysEqual(cols, myCols) ||
        !arraysEqual(colSpans, myColSpans)) {
      myHorzGridLines = horzGridLines;
      myVertGridLines = vertGridLines;
      myRows          = rows;
      myRowSpans      = rowSpans;
      myCols          = cols;
      myColSpans      = colSpans;
      repaint(container);
    }
  }

  private void repaint(final RadContainer container) {
    final int width = container.getWidth();
    final int height = container.getHeight();

    if (width * height >= 4096*4096) return;
    Graphics2D g2d = (Graphics2D) myImage.getGraphics();
    try {
      g2d.setComposite(AlphaComposite.Clear);
      g2d.fillRect(0, 0, width, height);

      g2d.setComposite(AlphaComposite.Src);
      if (container.isSelected()) {
        g2d.setColor(Painter.SELECTED_GRID_COLOR);
      }
      else {
        g2d.setColor(Painter.NON_SELECTED_GRID_COLOR);
      }

      g2d.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1.0f, ourDashes, 0.0f));

      if (myHorzGridLines.length > 0) {
        int lastLine = (container.getDelegee().getHeight() - myHorzGridLines [myHorzGridLines.length-1] > 4)
                       ? myHorzGridLines.length
                       : myHorzGridLines.length-1;
        for (int i = 1; i < lastLine; i++) {
          final int y = myHorzGridLines [i];
          g2d.drawLine(0, y, width, y);
        }
      }

      if (myVertGridLines.length > 0) {
        // Vertical lines
        int lastLine = (container.getDelegee().getWidth() - myVertGridLines [myVertGridLines.length-1] > 4)
                       ? myVertGridLines.length
                       : myVertGridLines.length-1;
        for (int i = 1; i < lastLine; i++) {
          final int x = myVertGridLines [i];
          g2d.drawLine(x, 0, x, height);
        }
      }

      g2d.setComposite(AlphaComposite.Clear);
      g2d.setStroke(new BasicStroke(1.0f));
      for(RadComponent childComponent: container.getComponents()) {
        final GridConstraints constraints = childComponent.getConstraints();
        if (constraints.getColSpan() > 1) {
          for(int col = constraints.getColumn()+1; col < constraints.getColumn() + constraints.getColSpan(); col++) {
            drawVertGridLine(g2d, col, constraints.getRow(), constraints.getRowSpan());
          }

        }
        if (constraints.getRowSpan() > 1) {
          for(int row = constraints.getRow()+1; row < constraints.getRow() + constraints.getRowSpan(); row++) {
            drawHorzGridLine(g2d, row, constraints.getColumn(), constraints.getColSpan());
          }
        }
      }
    }
    finally {
      g2d.dispose();
    }
  }

  private void drawVertGridLine(final Graphics2D g2d, final int col, final int row, final int rowSpan) {
    // protect against invalid constraints
    if (col < 0 || col >= myVertGridLines.length || row < 0 || row+rowSpan >= myHorzGridLines.length) return;
    g2d.drawLine(myVertGridLines [col],
                 myHorzGridLines [row]+4,
                 myVertGridLines [col],
                 myHorzGridLines [row+rowSpan]-4);
  }

  private void drawHorzGridLine(final Graphics2D g2d, final int row, final int col, final int colSpan) {
    // protect against invalid constraints
    if (col < 0 || col+colSpan >= myVertGridLines.length || row < 0 || row >= myHorzGridLines.length) return;
    g2d.drawLine(myVertGridLines [col]+4,
                 myHorzGridLines [row],
                 myVertGridLines [col + colSpan]-4,
                 myHorzGridLines [row]);
  }

  private static boolean arraysEqual(final int[] newArray, final int[] oldArray) {
    if (oldArray == null || newArray.length != oldArray.length) {
      return false;
    }
    for(int i=0; i<oldArray.length; i++) {
      if (newArray [i] != oldArray [i]) {
        return false;
      }
    }
    return true;
  }

  private boolean sizeEquals(final RadContainer container) {
    return myImage.getWidth(null) == container.getWidth() &&
           myImage.getHeight(null) == container.getHeight();
  }

  private Image getImage() {
    return myImage;
  }

  public static Image getGridImage(final RadContainer container) {
    //noinspection unchecked
    SoftReference<CachedGridImage> imageRef = (SoftReference<CachedGridImage>) container.getDelegee().getClientProperty(CACHED_GRID_IMAGE_KEY);
    CachedGridImage gridImage = dereference(imageRef);
    if (gridImage != null && gridImage.sizeEquals(container)) {
      gridImage.update(container);
    }
    else {
      gridImage = new CachedGridImage(container);
      container.getDelegee().putClientProperty(CACHED_GRID_IMAGE_KEY,
                                               new SoftReference<>(gridImage));
    }
    return gridImage.getImage();
  }
}
