/*
 * File    : ActiveBox.java
 * Created : 12-dec-2000 17:05
 * 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.jclic.PlayStation;
import edu.xtec.jclic.media.ActiveMediaPlayer;
import edu.xtec.jclic.misc.Utils;
import edu.xtec.util.JDomUtility;
import edu.xtec.util.Options;
import edu.xtec.util.ResourceBridge;
import edu.xtec.util.StrUtils;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.font.FontRenderContext;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.ImageObserver;
import java.text.AttributedString;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.RepaintManager;
import javax.swing.SwingUtilities;

/**
 * Objects of this class are widely used in JClic activities: cells in puzzles
 * and associations, messages and other objects are active boxes. The specific
 * content, size and location of <CODE>
 * ActiveBox</CODE> is determined by its
 * {@link edu.xtec.jclic.boxes.ActiveBoxContent} members. Most ActiveBoxes have
 * only one content, but some of them can have a secondary or "alternative"
 * content, indicated by the <CODE>altContent</CODE> member. This content is
 * used only when the <CODE>alternative</CODE> flag of the
 * <CODE>ActiveBox</CODE> is on. Active boxes can host video and interactive
 * media content (specified in the mediaContent member of the
 * {@link edu.xtec.jclic.boxes.ActiveBoxContent}) through the
 * <CODE>hostedMediaPlayer</CODE> member.
 *
 * @author Francesc Busquets (fbusquets@xtec.cat)
 * @version 13.08.28
 */
public class ActiveBox extends AbstractBox implements Cloneable {

  public static boolean compressImages = true;
  public static boolean USE_TRANSFORM = false;

  public int idOrder, idLoc, idAss;
  protected ActiveBoxContent content;
  protected ActiveBoxContent altContent;
  public boolean hasHostedComponent;
  protected ActiveMediaPlayer hostedMediaPlayer;

  /** Creates new ActiveBox */
  public ActiveBox(AbstractBox parent, JComponent container, BoxBase boxBase) {
    super(parent, container, boxBase);
    clear();
    idLoc = -1;
    idOrder = -1;
    idAss = -1;
    hasHostedComponent = false;
  }

  public ActiveBox(AbstractBox parent, JComponent container, int setIdLoc, Rectangle2D r, BoxBase boxBase) {
    super(parent, container, boxBase);
    clear();
    idLoc = setIdLoc;
    setBounds(r);
  }

  public void setHostedMediaPlayer(ActiveMediaPlayer amp) {
    ActiveMediaPlayer old = hostedMediaPlayer;
    hostedMediaPlayer = amp;
    if (old != null && old != amp)
      old.linkTo(null);
  }

  public ActiveBoxContent getCurrentContent() {
    return isAlternative() ? altContent : content;
  }

  public ActiveBoxContent getContent() {
    if (content == null)
      setContent(new ActiveBoxContent());
    return content;
  }

  public void clear() {
    content = null;
    altContent = null;
    idOrder = -1;
    setInactive(true);
    if (!hasHostedComponent)
      setHostedComponent(null);
    setHostedMediaPlayer(null);
  }

  public boolean isEquivalent(ActiveBox bx, boolean checkCase) {
    return bx != null && content != null && content.isEquivalent(bx.content, checkCase);
  }

  public boolean isCurrentContentEquivalent(ActiveBox bx, boolean checkCase) {
    return bx != null && getCurrentContent() != null
        && getCurrentContent().isEquivalent(bx.getCurrentContent(), checkCase);
  }

  public void exchangeLocation(ActiveBox bx) {
    Point2D.Double pt = new Point2D.Double(x, y);
    int idLoc0 = idLoc;
    setLocation(bx.getLocation());
    bx.setLocation(pt);
    idLoc = bx.idLoc;
    bx.idLoc = idLoc0;
  }

  public void copyContent(ActiveBox bx) {
    idOrder = bx.idOrder;
    idAss = bx.idAss;
    content = bx.content;
    altContent = bx.altContent;
    if (content != null) {
      if (content.bb != null)
        setBoxBase(content.bb);
      if (content.border != null && bx.hasBorder() != content.border.booleanValue())
        setBorder(content.border.booleanValue());
      if (content.img != null && content.animated)
        Utils.refreshAnimatedImage(content.img);
    }
    setInactive(bx.isInactive());
    setInverted(bx.isInverted());
    setAlternative(bx.isAlternative());
    setHostedComponent(bx.getHostedComponent());
    hasHostedComponent = bx.hasHostedComponent;
    setHostedMediaPlayer(bx.hostedMediaPlayer);
    if (hostedMediaPlayer != null)
      hostedMediaPlayer.setVisualComponentVisible(!isInactive() && isVisible());
  }

  public void exchangeContent(ActiveBox bx) {
    ActiveBox bx0 = new ActiveBox(getParent(), getContainerX(), getBoxBaseX());
    bx0.copyContent(this);
    copyContent(bx);
    bx.copyContent(bx0);
  }

  public void setTextContent(String tx) {
    // only plain text!
    if (tx == null)
      tx = "";
    if (content == null)
      content = new ActiveBoxContent();
    content.rawText = tx;
    content.text = tx;
    content.mediaContent = null;
    content.img = null;

    setHostedComponent(null);
    setInactive(false);
    checkHostedComponent();
    setHostedMediaPlayer(null);
  }

  public void setIdOrder(int newIdOrder) {
    idOrder = newIdOrder;
  }

  public void setIdAss(int newIdAss) {
    idAss = newIdAss;
  }

  public void setDefaultIdAss() {
    idAss = (content == null ? -1 : content.id);
  }

  public boolean isAtPlace() {
    return idOrder == idLoc;
  }

  public void setContent(ActiveBoxContent abc) {
    setHostedComponent(null);
    setHostedMediaPlayer(null);
    content = abc;
    if (content != null) {
      if (content.bb != getBoxBaseX())
        setBoxBase(content.bb);
      if (content.border != null && hasBorder() != content.border.booleanValue())
        setBorder(content.border.booleanValue());
      setInactive(false);
      if (abc.img != null && abc.animated)
        Utils.refreshAnimatedImage(abc.img);
      checkHostedComponent();
      checkAutoStartMedia();
    } else
      clear();
  }

  public void checkAutoStartMedia() {
    ActiveBoxContent cnt = getContent();
    if (cnt != null && cnt.mediaContent != null && cnt.mediaContent.autoStart == true && cnt.amp != null) {
      final ActiveMediaPlayer amp = cnt.amp;
      SwingUtilities.invokeLater(new Runnable() {
        public void run() {
          amp.play(ActiveBox.this);
        }
      });
    }
  }

  public void setAltContent(ActiveBoxContent abc) {
    altContent = abc;
    checkHostedComponent();
    if (isAlternative() && hostedMediaPlayer != null)
      setHostedMediaPlayer(null);
  }

  public void setCurrentContent(ActiveBoxContent abc) {
    if (isAlternative())
      setAltContent(abc);
    else
      setContent(abc);
    repaint();
  }

  public void setContent(ActiveBagContent abc, int i) {
    if (i < 0)
      i = idOrder;
    if (abc == null || i >= abc.getNumCells())
      return;
    if (abc.bb != getBoxBaseX())
      setBoxBase(abc.bb);
    setContent(abc.getActiveBoxContent(i));
  }

  public void setAltContent(ActiveBagContent abc, int i) {
    if (i < 0)
      i = idOrder;
    if (abc == null || abc.isEmpty() || i > abc.getNumCells())
      return;
    setAltContent(abc.getActiveBoxContent(i));
  }

  public boolean switchToAlt(ResourceBridge rb) {
    if (isAlternative() || altContent == null || altContent.isEmpty())
      return false;
    setHostedComponent(null);
    setHostedMediaPlayer(null);
    setAlternative(true);
    checkHostedComponent();
    checkAutoStartMedia();
    return true;
  }

  @Override
  protected void checkHostedComponent() {
    if (hasHostedComponent)
      return;
    ActiveBoxContent abc = getCurrentContent();
    BoxBase bb = getBoxBaseResolve();
    Component jc = null;
    if (!isInactive() && abc != null && abc.htmlText != null) {
      String s = abc.htmlText;
      if (abc.innerHtmlText != null) {
        Color backColor = isInactive() ? bb.inactiveColor : isInverted() ? bb.textColor : bb.backColor;
        Color foreColor = isInverted() ? bb.backColor : isAlternative() ? bb.alternativeColor : bb.textColor;
        Font f = bb.getOriginalFont();
        s = "<html>" + "<body bgcolor=\"#" + Integer.toHexString(backColor.getRGB() & 0xFFFFFF) + "\">"
            + "<div style=\"" + "background: #" + Integer.toHexString(backColor.getRGB() & 0xFFFFFF) + "; " + "color: #"
            + Integer.toHexString(foreColor.getRGB() & 0xFFFFFF) + "; " + "font-family: " + f.getFontName() + "; "
            + "font-size: " + f.getSize() + "pt; " + (f.isBold() ? "font-weight: bold;" : "") + "text-align: "
            + (abc.txtAlign[0] == JDomUtility.ALIGN_LEFT ? "left"
                : abc.txtAlign[0] == JDomUtility.ALIGN_RIGHT ? "right" : "center")
            + "; " + "width: 100%; " + "\">" + abc.innerHtmlText + "</div>" + "</body></html>";
      }
      jc = getHostedComponent();
      if (jc != null && jc instanceof JLabel) {
        ((JLabel) jc).setText(s);
        return;
      } else {
        jc = new JLabel(s);
      }
    }
    setHostedComponent(jc);
  }

  public boolean updateContent(Graphics2D g2, Rectangle dirtyRegion, ImageObserver io) {

    ActiveBoxContent abc = getCurrentContent();
    BoxBase bb = getBoxBaseResolve();

    if (isInactive() || abc == null || width < 2 || height < 2)
      return true;

    Rectangle2D.Double imgRect = null;

    if (abc.img != null) {
      if (abc.imgClip != null) {
        Rectangle r = abc.imgClip.getBounds();

        /*
         * We have two methods to draw: # Using AffineTransform * Compatible with Mac
         * OS-X 10.1, JRE 1.3.1 rev 1. * Possibly more slow ?
         *
         * # Using Graphics.drawImage * Doesn't work with Mac OSX 10.1 Java 1.3.1 rev. 1
         *
         * Comment one of the two, or apply method 1 only to objects of class:
         * com.apple.mrj.internal.awt.graphics.VImage
         */

        // Method 1
        if (USE_TRANSFORM) {
          if (r.width > 0 && r.height > 0) {
            AffineTransform at;
            if (r.width != width || r.height != height) {
              double sx = width / r.width;
              double sy = height / r.height;
              at = AffineTransform.getScaleInstance(sx, sy);
              at.translate(x / sx - r.x, y / sy - r.y);
            } else
              at = AffineTransform.getTranslateInstance(x - r.x, y - r.y);
            g2.drawImage(abc.img, at, io);
          }
        } else {
          // Method 2
          g2.drawImage(abc.img, (int) x, (int) y, (int) (x + width), (int) (y + height), (int) r.x, (int) r.y,
              (int) (r.x + r.width), (int) (r.y + r.height), io);
        }
      } else {
        double imgw, imgh;
        boolean compress = false;
        if ((imgw = abc.img.getWidth(io)) == 0)
          imgw = width;
        if ((imgh = abc.img.getHeight(io)) == 0)
          imgh = height;
        double scale = 1.0;
        if (compressImages == true && (width > 0 && height > 0) && (imgw > width || imgh > height)) {
          scale = Math.min(width / imgw, height / imgh);
          imgw *= scale;
          imgh *= scale;
          compress = true;
        }
        double xs = (abc.imgAlign[0] == JDomUtility.ALIGN_LEFT ? 0
            : abc.imgAlign[0] == JDomUtility.ALIGN_RIGHT ? width - imgw : (width - imgw) / 2);
        double ys = (abc.imgAlign[1] == JDomUtility.ALIGN_TOP ? 0
            : abc.imgAlign[1] == JDomUtility.ALIGN_BOTTOM ? height - imgh : (height - imgh) / 2);
        if (compress) {
          if (USE_TRANSFORM) {
            // Method 1:
            AffineTransform at = AffineTransform.getScaleInstance(scale, scale);
            at.translate((x + xs) / scale, (y + ys) / scale);
            g2.drawImage(abc.img, at, io);
          } else {
            // Method 2:
            g2.drawImage(abc.img, (int) (x + xs), (int) (y + ys), (int) imgw, (int) imgh, io);
          }
        } else
          g2.drawImage(abc.img, (int) (x + xs), (int) (y + ys), io);

        if (abc.avoidOverlapping && abc.text != null /* && !JDomUtility.isDefaultAlign(abc.imgAlign) */)
          imgRect = new Rectangle2D.Double(Math.max(0.0, xs), Math.max(0.0, ys), Math.min(width, imgw),
              Math.min(height, imgh));
      }
    }
    if (abc.text != null && abc.text.length() > 0) {
      double px = this.x;
      double py = this.y;
      double pWidth = this.width;
      double pHeight = this.height;
      if (imgRect != null) {
        double[] prx = new double[] { 0, imgRect.x, imgRect.x + imgRect.width, pWidth };
        double[] pry = new double[] { 0, imgRect.y, imgRect.y + imgRect.height, pHeight };
        Rectangle2D.Double[] rr = new Rectangle2D.Double[] { new Rectangle2D.Double(prx[0], pry[0], prx[3], pry[1]),
            new Rectangle2D.Double(prx[0], pry[2], prx[3], pry[3] - pry[2]),
            new Rectangle2D.Double(prx[0], pry[0], prx[1], pry[3]),
            new Rectangle2D.Double(prx[2], pry[0], prx[3] - prx[2], pry[3]) };
        Rectangle2D.Double rmax = rr[0];
        double maxSurface = rmax.width * rmax.height;
        for (int i = 1; i < rr.length; i++) {
          double s = rr[i].width * rr[i].height;
          if (s > maxSurface - 1) {
            if (Math.abs(s - maxSurface) <= 1) {
              boolean b = false;
              switch (i) {
              case 1:
                b = (abc.txtAlign[1] == JDomUtility.ALIGN_BOTTOM);
                break;
              case 2:
                b = (abc.txtAlign[0] == JDomUtility.ALIGN_LEFT);
                break;
              case 3:
                b = (abc.txtAlign[0] == JDomUtility.ALIGN_RIGHT);
                break;
              }
              if (!b)
                continue;
            }
            maxSurface = s;
            rmax = rr[i];
          }
        }
        px += rmax.x;
        py += rmax.y;
        pWidth = rmax.width;
        pHeight = rmax.height;
      }
      double w, wtl, h, hl, dx, dy;
      double ww = Math.max(5.0, pWidth - 2 * bb.textMargin);
      AttributedString atext;
      TextLayout layout;
      int rt;
      FontRenderContext frc = g2.getFontRenderContext();
      LineBreakMeasurer lbm;
      int maxLines = StrUtils.countSpaces(abc.text) + 1;

      for (;;) {
        int i;
        atext = new AttributedString(abc.text);
        atext.addAttribute(TextAttribute.FONT, bb.getFont());
        lbm = new LineBreakMeasurer(atext.getIterator(), frc);
        w = 0;
        h = 0;
        hl = 0;
        lbm.setPosition(0);
        rt = abc.text.indexOf('\n');
        for (i = 0; lbm.getPosition() < abc.text.length() && h < pHeight; i++) {
          if (rt > lbm.getPosition()) {
            layout = lbm.nextLayout((float) ww, rt, false);
            rt = abc.text.indexOf('\n', lbm.getPosition());
          } else
            layout = lbm.nextLayout((float) ww);

          if (layout == null)
            break;
          wtl = layout.getVisibleAdvance();
          if (wtl > w)
            w = wtl;
          hl = layout.getLeading();
          h += layout.getAscent() + layout.getDescent() + hl;
        }
        h -= hl;
        if ((h <= pHeight && i <= maxLines) || bb.reduceFont() == false)
          break;
        JComponent jc = getContainerResolve();
        if (jc != null)
          RepaintManager.currentManager(jc).markCompletelyDirty(jc);
      }
      dy = py + (abc.txtAlign[1] == JDomUtility.ALIGN_TOP ? 0
          : abc.txtAlign[1] == JDomUtility.ALIGN_BOTTOM ? pHeight - h : (pHeight - h) / 2);
      g2.setColor(isInverted() ? bb.backColor : isAlternative() ? bb.alternativeColor : bb.textColor);
      lbm.setPosition(0);
      rt = abc.text.indexOf('\n');
      h = 0;
      while (lbm.getPosition() < abc.text.length() && h < pHeight) {

        if (rt > lbm.getPosition()) {
          layout = lbm.nextLayout((float) ww, rt, false);
          rt = abc.text.indexOf('\n', lbm.getPosition());
        } else
          layout = lbm.nextLayout((float) ww);

        if (layout == null)
          break;
        wtl = layout.getVisibleAdvance();
        dx = px + bb.textMargin + (abc.txtAlign[0] == JDomUtility.ALIGN_LEFT ? 0
            : abc.txtAlign[0] == JDomUtility.ALIGN_RIGHT ? ww - wtl : (ww - wtl) / 2);

        h += layout.getAscent();
        if (bb.shadow) {
          g2.setColor(bb.shadowColor);
          layout.draw(g2, (float) (dx + bb.getDynFontSize() / 10), (float) (dy + h + bb.getDynFontSize() / 10));
          g2.setColor(isInverted() ? bb.backColor : isAlternative() ? bb.alternativeColor : bb.textColor);
        }
        layout.draw(g2, (float) dx, (float) (dy + h));
        h += layout.getDescent() + layout.getLeading();
      }
    }
    return true;
  }

  public boolean playMedia(PlayStation ps) {
    ActiveBoxContent abc = getCurrentContent();
    // ORIGINAL
    /*
     * if(abc!=null && abc.mediaContent!=null){ ac.playMedia(abc.mediaContent,
     * this); return true; }
     */
    // MODIFIED TO REPAINT ANIMATED GIFS
    if (abc != null) {
      if (abc.animated) {
        Utils.refreshAnimatedImage(abc.img);
        repaint();
      }
      if (abc.mediaContent != null) {
        ps.playMedia(abc.mediaContent, this);
        return true;
      }
    }
    return false;
  }

  public String getDescription() {
    return content == null ? "" : content.getDescription();
  }

  @Override
  public void setBounds(Rectangle2D r) {
    super.setBounds(r);
    if (hostedMediaPlayer != null)
      hostedMediaPlayer.checkVisualComponentBounds(this);
  }

  @Override
  public void end() {
    clear();
    super.end();
  }

  public static void checkOptions(Options options) {
    if (!options.getBoolean(Options.JAVA14) && options.getBoolean(Options.MAC)) {
      USE_TRANSFORM = true;
    }
  }
}
