/*
 * File    : ThumbsExplorerPanel.java
 * Created : 11-feb-2004 11:44
 * By      : fbusquets
 *
 * JClic - Authoring and playing system for educational activities
 *
 * Copyright (C) 2000 - 2018 Francesc Busquets & Departament
 * d'Educacio de la Generalitat de Catalunya
 *
 * 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 (see the LICENSE file).
 */

package edu.xtec.jclic.boxes;

import edu.xtec.util.LFUtil;
import java.awt.AWTEvent;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.font.FontRenderContext;
import java.awt.font.TextHitInfo;
import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
import javax.swing.event.EventListenerList;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

/**
 * This class is a {@link javax.swing.JPanel} that contains a set of
 * {@link edu.xtec.jclic.boxes.ThumbsExplorerPanel.ThumbElement} objects.
 * Elements are displayed into a grid of rectangular cells of same size,
 * distributed from left to right and top to bottom. It provides methods to
 * select a specific ThumbElement, and has a
 * {@link javax.swing.event.EventListenerList} used to notify observers about
 * selection changes.
 *
 * @author Francesc Busquets (fbusquets@xtec.cat)
 * @version 13.08.13
 */
public class ThumbsExplorerPanel extends JPanel {

  public static final int DEFAULT_THUMB_WIDTH = 90;
  public static final int DEFAULT_THUMB_HEIGHT = 90;
  public static final int DEFAULT_THUMB_MARGIN = 14;
  public static final int DEFAULT_TEXT_HEIGHT = 14;
  public static final int DEFAULT_THUMB_INTERNAL_MARGIN = 2;

  protected static BasicStroke BORDER_STROKE = new BasicStroke(0.2F);
  protected int th_width, th_height, th_margin, th_textHeight, th_int_margin;
  protected Font font;

  private List<ThumbElement> elements;
  private ThumbElement current;
  private int elementsPerRow;

  private EventListenerList listenersList;
  public Dimension maxThumbSize;
  public Dimension boxSize;

  /** Creates a new instance of ThumbsExplorerPanel */
  public ThumbsExplorerPanel() {
    setLayout(null);
    elements = new ArrayList<ThumbElement>();
    listenersList = new EventListenerList();
    setSizes(DEFAULT_THUMB_WIDTH, DEFAULT_THUMB_HEIGHT, DEFAULT_THUMB_MARGIN, DEFAULT_TEXT_HEIGHT,
        DEFAULT_THUMB_INTERNAL_MARGIN);
    setPreferredSize(new Dimension(3 * boxSize.width, boxSize.height));
    setFont(new Font("Dialog", Font.PLAIN, 12));
    setBackground(LFUtil.getColor("Table.background", Color.lightGray));
    elementsPerRow = 1;
    enableEvents(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.KEY_EVENT_MASK);
  }

  public void setSizes(int th_width, int th_height, int th_margin, int th_textHeight, int th_int_margin) {
    this.th_width = Math.max(10, th_width);
    this.th_height = Math.max(10, th_height);
    this.th_margin = Math.max(5, th_margin);
    this.th_textHeight = Math.max(10, th_textHeight);
    this.th_int_margin = Math.max(0, th_int_margin);
    boxSize = new Dimension(th_width + 2 * th_margin, th_height + th_textHeight + 2 * th_margin);
    maxThumbSize = new Dimension(th_width - 2 * th_int_margin, th_height - 2 * th_int_margin);
    for (int i = 0; i < elements.size(); i++) {
      getThumbElement(i).sizeImage();
    }
    invalidate();
  }

  public void resizeTo(int desiredWidth) {
    int numElements = elements.size();
    int w = Math.max(boxSize.width, desiredWidth);
    int bpr = w / boxSize.width;
    int numRows = (numElements / bpr) + 1;
    int h = numRows * boxSize.height;
    setPreferredSize(new Dimension(w, h));
    revalidate();
    repaint();
  }

  public ThumbElement getThumbElement(int p) {
    return elements.get(p);
  }

  public ThumbElement[] getThumbElements() {
    return elements.toArray(new ThumbElement[elements.size()]);
  }

  public ThumbElement getElementFor(Object object) {
    ThumbElement result = null;
    for (int i = 0; i < elements.size(); i++) {
      if (getThumbElement(i).getUserObject() == object) {
        result = getThumbElement(i);
        break;
      }
    }
    return result;
  }

  public int getThumbElementCount() {
    return elements.size();
  }

  public void removeThumbElementAt(int p) {
    if (elements != null) {
      ThumbElement o = elements.get(p);
      if (o != null) {
        if (o == current)
          current = null;
        elements.remove(p);
        invalidate();
      }
    }
  }

  public void removeAllThumbElements() {
    elements.clear();
    current = null;
    invalidate();
    repaint();
  }

  public ThumbElement addThumbElement(int p, Object userObject, ImageIcon img, String text) {
    ThumbElement th = new ThumbElement(userObject, img, text);
    if (p >= 0)
      elements.add(p, th);
    else
      elements.add(th);
    invalidate();
    return th;
  }

  public ThumbElement addThumbElement(Object userObject, ImageIcon img, String text) {
    return addThumbElement(-1, userObject, img, text);
  }

  /**
   * Getter for property current.
   *
   * @return Value of property current.
   */
  public ThumbElement getCurrent() {
    return current;
  }

  public Object getCurrentObject() {
    return current == null ? null : current.getUserObject();
  }

  /**
   * Setter for property current.
   *
   * @param th New value of {@link #current}.
   */
  public void setCurrent(ThumbElement th) {
    if (current != null) {
      Rectangle r = getRectFor(getIndexOf(current));
      if (r != null)
        repaint(r);
    }
    if (th != null && elements.contains(th)) {
      current = th;
      Rectangle r = getRectFor(getIndexOf(current));
      if (r != null)
        repaint(r);
      scrollRectToVisible(r);
    } else
      current = null;
  }

  public void checkCurrentVisibility() {
    if (current != null) {
      Rectangle r = getRectFor(getIndexOf(current));
      if (r != null)
        scrollRectToVisible(r);
    }
  }

  public void setCurrentObject(Object o) {
    ThumbElement th = null;
    for (int i = 0; i < elements.size(); i++) {
      if (getThumbElement(i).getUserObject() == o) {
        th = getThumbElement(i);
        break;
      }
    }
    setCurrent(th);
  }

  /**
   * Getter for property font.
   *
   * @return Value of property font.
   */
  @Override
  public Font getFont() {
    return font;
  }

  /**
   * Setter for property font.
   *
   * @param font New value of property font.
   */
  @Override
  public void setFont(Font font) {
    this.font = font;
    repaint();
  }

  public int getIndexOf(ThumbElement th) {
    return th == null ? -1 : elements.indexOf(th);
  }

  public Rectangle getRectFor(int p) {
    Rectangle r = null;
    if (p >= 0 && p < elements.size()) {
      r = new Rectangle(boxSize);
      r.x = (p % elementsPerRow) * r.width;
      r.y = (p / elementsPerRow) * r.height;
    }
    return r;
  }

  @Override
  public void doLayout() {
    Dimension d = getPreferredSize();
    int w = Math.max(d.width, boxSize.width);
    elementsPerRow = w / boxSize.width;
    /*
     * ELIMINATED DUE TO RECURSIVE CALLS IN 1.4 TEST IN 1.3.1
     *
     * int nRows=elements.size()/elementsPerRow+1; int h=nRows*boxSize.height;
     * if(getHeight()!=h) setBounds(getX(), getY(), w, h);
     */
  }

  @Override
  public void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2 = (Graphics2D) g;
    Rectangle clip = g2.getClipBounds();
    Rectangle r = new Rectangle(boxSize);
    for (int i = 0; i < elements.size(); i++) {
      r.x = (i % elementsPerRow) * r.width;
      r.y = (i / elementsPerRow) * r.height;
      if (r.intersects(clip)) {
        getThumbElement(i).paint(g2, r);
      }
    }
  }

  public void addActionListener(ActionListener l) {
    listenersList.add(ActionListener.class, l);
  }

  public void removeActionListener(ActionListener l) {
    listenersList.remove(ActionListener.class, l);
  }

  protected void fireActionPerformed() {
    ActionEvent actionEvent = new ActionEvent(current, ActionEvent.ACTION_PERFORMED, "");
    Object[] listeners = listenersList.getListeners(ActionListener.class);
    for (Object lst : listeners) {
      ((ActionListener) lst).actionPerformed(actionEvent);
    }
  }

  public void addListSelectionListener(ListSelectionListener l) {
    listenersList.add(ListSelectionListener.class, l);
  }

  public void removeListSelectionListener(ListSelectionListener l) {
    listenersList.remove(ListSelectionListener.class, l);
  }

  protected void fireSelectionChanged(int index) {
    ListSelectionEvent listEvent = new ListSelectionEvent(current, index, index, false);
    Object[] listeners = listenersList.getListeners(ListSelectionListener.class);
    for (Object lst : listeners) {
      ((ListSelectionListener) lst).valueChanged(listEvent);
    }
  }

  @Override
  protected void processMouseEvent(MouseEvent ev) {
    if (isEnabled() && ev.getID() == MouseEvent.MOUSE_RELEASED) {
      int p = (ev.getX() / boxSize.width) + elementsPerRow * (ev.getY() / boxSize.height);
      if (p < elements.size()) {
        if (ev.getClickCount() == 1) {
          setCurrent(getThumbElement(p));
          fireSelectionChanged(p);
        } else if (ev.getClickCount() == 2 /* && current==getThumbElement(p) */) {
          fireActionPerformed();
        }
      }
    }
  }

  @Override
  protected void processKeyEvent(KeyEvent e) {
  }

  public class ThumbElement {

    ImageIcon image;
    String text;
    Object userObject;

    /** Creates a new instance of ThumbElement */
    ThumbElement(Object userObject, ImageIcon image, String text) {
      setUserObject(userObject);
      setImage(image);
      setText(text);
    }

    /**
     * Getter for property userObject.
     *
     * @return Value of property userObject.
     */
    public java.lang.Object getUserObject() {
      return userObject;
    }

    /**
     * Setter for property userObject.
     *
     * @param userObject New value of property userObject.
     */
    public void setUserObject(java.lang.Object userObject) {
      this.userObject = userObject;
    }

    /**
     * Getter for property text.
     *
     * @return Value of property text.
     */
    public java.lang.String getText() {
      return text;
    }

    /**
     * Setter for property text.
     *
     * @param text New value of property text.
     */
    public void setText(java.lang.String text) {
      this.text = text;
    }

    /**
     * Getter for property image.
     *
     * @return Value of property image.
     */
    public ImageIcon getImage() {
      return image;
    }

    /**
     * Setter for property image.
     *
     * @param image New value of property image.
     */
    public void setImage(ImageIcon image) {
      this.image = image;
      if (image != null)
        sizeImage();
    }

    protected void sizeImage() {
      if (image != null) {
        int w = image.getIconWidth();
        int h = image.getIconHeight();
        if (w > maxThumbSize.width || h > maxThumbSize.height) {
          double f = Math.min((double) maxThumbSize.width / w, (double) maxThumbSize.height / h);
          image = new ImageIcon(image.getImage().getScaledInstance((int) (f * w), (int) (f * h), Image.SCALE_SMOOTH));
        }
      }
    }

    protected void paint(Graphics2D g2, Rectangle r) {

      g2.setBackground(LFUtil.getColor("Table.background", Color.white));
      g2.clearRect(r.x, r.y, r.width, r.height);

      if (current == this) {
        Rectangle r2 = new Rectangle(r.x + th_margin / 2, r.y + th_margin / 2, r.width - th_margin,
            r.height - th_margin);
        g2.setColor(LFUtil.getSysColor("activeCaption", Color.orange));
        g2.fill(r2);
        g2.setColor(LFUtil.getSysColor("activeCaptionBorder", Color.red));
        g2.draw(r2);
      }
      if (image != null) {
        image.paintIcon(ThumbsExplorerPanel.this, g2, r.x + ((th_width + 2 * th_margin) - image.getIconWidth()) / 2,
            r.y + ((th_height + 2 * th_margin) - image.getIconHeight()) / 2);
      }
      g2.setColor(LFUtil.getColor("Table.gridColor", Color.gray));
      g2.setStroke(BORDER_STROKE);
      g2.drawRect(r.x + th_margin, r.y + th_margin, th_width, th_height);
      if (text != null && text.length() > 0) {
        if (current == this)
          g2.setColor(LFUtil.getSysColor("activeCaptionText", Color.black));
        else
          g2.setColor(LFUtil.getColor("Table.foreground", Color.black));
        FontRenderContext frc = g2.getFontRenderContext();
        Font f2 = LFUtil.getFont("Table.font", font);
        TextLayout layout = new TextLayout(text, f2, frc);
        Rectangle2D txRect = layout.getBounds();
        if (layout.getBounds().getWidth() > th_width) {
          TextHitInfo hitInfo = layout.hitTestChar(th_width - 20, 0);
          int insPoint = hitInfo.getInsertionIndex();
          if (insPoint > 0) {
            layout = new TextLayout(text.substring(0, insPoint) + "...", f2, frc);
            txRect = layout.getBounds();
          }
        }
        layout.draw(g2, (float) r.x + th_margin + (th_width - (float) txRect.getWidth()) / 2,
            (float) r.y + 2 * th_margin + th_height);
      }
    }
  }
}
