/*
 * Copyright (c) 2010-2020, sikuli.org, sikulix.com - MIT license
 */

package org.sikuli.util;

import org.sikuli.basics.Debug;
import org.sikuli.basics.Settings;
import org.sikuli.script.Location;
import org.sikuli.script.Region;

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

public class Highlight extends JFrame {

  static boolean hasTL = false;
  static boolean hasPPTL = false;
  static boolean hasPPTP = false;
  static double gdW = -1;
  static double gdH = -1;

  static List<Highlight> highlights = new ArrayList<>();

  static {
    GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
    GraphicsDevice gd = ge.getDefaultScreenDevice();
    gdW = gd.getDefaultConfiguration().getBounds().getWidth();
    gdH = gd.getDefaultConfiguration().getBounds().getHeight();
    hasTL = gd.isWindowTranslucencySupported(GraphicsDevice.WindowTranslucency.TRANSLUCENT);
    hasPPTL = gd.isWindowTranslucencySupported(GraphicsDevice.WindowTranslucency.PERPIXEL_TRANSLUCENT);
    hasPPTP = gd.isWindowTranslucencySupported(GraphicsDevice.WindowTranslucency.PERPIXEL_TRANSPARENT);
  }

  int side = 70;
  float halfSide = side * 0.5f;
  int locx = -1;
  int locy = -1;
  int sidex = -1;
  int sidey = -1;
  int frameX = -1;
  int frameY = -1;
  Region region = null;

  public boolean isShowable() {
    return showable;
  }

  boolean showable = false;

  private Highlight() {
    if (!hasPPTL) {
      Debug.error("Highlight: is not supported in your environment");
      Debug.error("Highlight: TansparencySupport: TL: %s PPTL: %s PPTP: %s", Highlight.hasTL, Highlight.hasPPTL, Highlight.hasPPTP);
    } else {
      showable = true;
      setUndecorated(true);
      setBackground(new Color(0, 0, 0, 0));
      setAlwaysOnTop(true);
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      setType(JFrame.Type.UTILITY);
      setFocusableWindowState(false);
      setAutoRequestFocus(false);
    }
  }

  public Highlight(Location loc) {
    this();
    if (isShowable()) {
      locx = loc.x;
      locy = loc.y;
      JPanel panel = initAsCross();
      setContentPane(panel);
    }
  }

  public Highlight(Region reg) {
    this(reg, null);
  }

  public Highlight(Region reg, String color) {
    this();
    if (isShowable()) {
      givenColor = evalColor(color);
      region = reg;
      locx = reg.x;
      locy = reg.y;
      sidex = reg.h;
      sidey = reg.w;
      JPanel panel = initAsFrame();
      if (panel == null) {
        getRootPane().setBorder(BorderFactory.createLineBorder(givenColor, 3));
      } else {
        setContentPane(panel);
      }
    }
  }

  private JPanel initAsCross() {
    setSize(side, side);
    frameX = locx - (int) halfSide;
    frameY = locy - (int) halfSide;
    JPanel panel = new JPanel() {
      @Override
      protected void paintComponent(Graphics g) {
        if (g instanceof Graphics2D) {
          int side = getWidth();
          Point2D.Float center = new Point2D.Float(halfSide, halfSide);
          Color redFull = new Color(255, 0, 0, 150);
          Color redTrans = new Color(255, 0, 0, 0);
          float radius = halfSide;
          float[] dist = {0.0f, 1.0f};
          Color[] colorsRed = {redFull, redTrans};
          Color[] colorsBlack = {Color.BLACK, Color.WHITE};
          Graphics2D g2d = (Graphics2D) g;
          g2d.setPaint(new RadialGradientPaint(center, radius, dist, colorsRed));
          g2d.fillRect(0, 0, side, side);
          g2d.setPaint(new RadialGradientPaint(center, radius, dist, colorsBlack));
          g2d.fillRect((int) halfSide - 1, 0, 2, side);
          g2d.fillRect(0, (int) halfSide - 1, side, 2);
        }
      }
    };
    return panel;
  }

  private int defaultOpaque = 30;
  private Color defaultColor = new Color(255, 0, 0, defaultOpaque);
  private Color givenColor = null;

  private Color getColor() {
    if (null == givenColor) {
      return defaultColor;
    }
    return new Color(givenColor.getRed(), givenColor.getGreen(), givenColor.getBlue(), defaultOpaque);
  }

  private Color evalColor(String color) {
    Color targetColor = Color.RED;
    if (color == null) {
      color = Settings.DefaultHighlightColor;
    }
    if (color.startsWith("#")) {
      if (color.length() > 7) {
        // might be the version #nnnnnnnnn
        if (color.length() == 10) {
          int cR = 255, cG = 0, cB = 0;
          try {
            cR = Integer.decode(color.substring(1, 4));
            cG = Integer.decode(color.substring(4, 7));
            cB = Integer.decode(color.substring(7, 10));
          } catch (NumberFormatException ex) {
          }
          try {
            targetColor = new Color(cR, cG, cB);
          } catch (IllegalArgumentException ex) {
          }
        }
      } else {
        // supposing it is a hex value
        try {
          targetColor = new Color(Integer.decode(color));
        } catch (NumberFormatException nex) {
        }
      }
    } else {
      // supposing color contains one of the defined color names
      if (!color.endsWith("Gray") || "Gray".equals(color)) {
        // the name might be given with any mix of lower/upper-case
        // only lightGray, LIGHT_GRAY, darkGray and DARK_GRAY must be given exactly
        color = color.toUpperCase();
      }
      try {
        Field field = Class.forName("java.awt.Color").getField(color);
        targetColor = (Color) field.get(null);
      } catch (Exception e) {
      }
    }
    return targetColor;
  }

  private JPanel initAsFrame() {
    int lineWidth = 3;
    frameX = locx - lineWidth;
    frameY = locy - lineWidth;
    int frameW = sidey + 2 * lineWidth;
    int frameH = sidex + 2 * lineWidth;
    setSize(frameW, frameH);
    if (Settings.HighlightTransparent) {
      return null;
    }
    JPanel panel = new JPanel() {
      @Override
      protected void paintComponent(Graphics g) {
        if (g instanceof Graphics2D) {
          Point2D.Float center = new Point2D.Float(frameW / 2.0f, frameH / 2.0f);
          Color colorFull = getColor();
          Color colorTrans = new Color(colorFull.getRed(), colorFull.getGreen(), colorFull.getBlue(), 0);
          float radius = Math.max(center.x, center.y);
          float[] dist = {0.0f, 1.0f};
          Color[] colors = {colorTrans, colorFull};
          Graphics2D g2d = (Graphics2D) g;
          g2d.setPaint(new RadialGradientPaint(center, radius, dist, colors));
          g2d.fillRect(0, 0, frameW, frameH);
        }
      }
    };
    panel.setBorder(BorderFactory.createLineBorder(givenColor, 3));
    return panel;
  }

  public static Highlight fakeHighlight() {
    //TODO fakeHighlightOn
    return null;
  }

  public Highlight doShow() {
    return doShow(0);
  }

  public Highlight doShow(double secs) {
    if (isShowable()) {
      setLocation(frameX, frameY);
      addMouseListener(new MouseAdapter() {
        @Override
        public void mouseReleased(MouseEvent e) {
          setVisible(false);
          dispose();
        }
      });
      synchronized (Highlight.class) {
        if (!highlights.contains(this)) {
          highlights.add(this);
        }
      }
      SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
          setVisible(true);
        }
      });
      if (secs > 0) {
        try {
          Thread.sleep((int) (1000 * (secs + Settings.WaitAfterHighlight)));
        } catch (InterruptedException ex) {
        }
        close();
      }
    }
    return this;
  }

  public void unShow() {
    if (isShowable()) {
      setVisible(false);
    }
  }

  public void close() {
    if (isShowable()) {
      setVisible(false);
    }
    dispose();
    showable = false;
    if (null != region) {
      region.internalUseOnlyHighlightReset();
    }
    if (inCloseAll) {
      return;
    }
    synchronized (Highlight.class) {
      highlights.remove(this);
    }
  }

  private static boolean inCloseAll = false;

  public static void closeAll(long time) {
    try {
      Thread.sleep(time * 1000);
    } catch (InterruptedException e) {
    }
    closeAll();
  }

  public static void closeAll() {
    synchronized (Highlight.class) {
      if (highlights.size() > 0) {
        inCloseAll = true;
        for (Highlight highlight : highlights) {
          highlight.close();
        }
        highlights.clear();
        inCloseAll = false;
      }
    }
  }
}
