/*
 * Copyright (c) 2009, Takeyuki Nagao
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the
 * following conditions are met:
 * 
 *  * Redistributions of source code must retain the above
 *    copyright notice, this list of conditions and the
 *    following disclaimer.
 *  * Redistributions in binary form must reproduce the above
 *    copyright notice, this list of conditions and the
 *    following disclaimer in the documentation and/or other
 *    materials provided with the distribution.
 *    
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 */

package dvi.gui.swing;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Logger;

import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.EventListenerList;

import dvi.DviException;
import dvi.DviRect;
import dvi.api.DviContext;
import dvi.api.DviContextSupport;
import dvi.api.DviPage;
import dvi.ctx.DviToolkit;
import dvi.util.DaemonThreadFactory;

public class TDviPage extends JPanel implements DviContextSupport, ChangeListener
{
  private static final Logger LOGGER = Logger.getLogger(TDviPage.class.getName());
  private static final long serialVersionUID = -7167678585159838308L;
  
  private boolean busy = false;
  
  private final DviContextSupport dcs;
  public DviContext getDviContext() { return dcs.getDviContext(); }

  public TDviPage(DviContextSupport dcs, DviPage page) {
    super();
    this.dcs = dcs;
    this.viewSpec = new ViewSpec(this);
    this.viewSpec.setResolution(viewSpec.getResolution().approximate(200));
    setPage(page);
  }
  
  public TDviPage(DviContextSupport dcs) {
    this(dcs, null);
  }

  private DviPage page = null;

  public void setPage(DviPage page)
  {
    this.page = page;
    determineSize();
  }
  
  public DviPage getPage() { return page; }

  private ViewSpec viewSpec;

  public void setViewSpec(ViewSpec viewSpec)
  {
    LOGGER.fine("page=" + page + ",viewSpec=" + viewSpec);
    this.viewSpec = viewSpec;
    determineSize();
  }
  
  public ViewSpec getViewSpec() { return viewSpec; }

//  private DviRect bbox;
//
//  public void setBoundingBox(DviRect bbox)
//  {
//    this.bbox = bbox;
//    determineSize();
//  }
//
//  public DviRect getBoundingBox()
//  {
//    return bbox;
//  }

//  private Anchor.Href myHref = null;
//  private DviRect saveBBOX = null;
//
//  public ByteRange getByteRangeFor(DviRect rect)
//  {
//    try {
//      DviResolution res = viewSpec.getResolution();
//
//      rect = rect.intersect(displayRect);
//      if (rect.isEmpty())
//        return null;
//      rect = rect.magnify(res.shrinkFactor());
//
//      Geometer geometer = new BasicGeometer(this);
//      ByteRangeComputer brc = new ByteRangeComputer(this, res, rect);
//      geometer.setPainter(brc);
//      getDviContext().execute(page, geometer);
//      AnchorSet anchors = (AnchorSet) rsc.getDataSource().find(AnchorSet.class,
//          page);
//      if (myHref != null) {
//        anchors.remove(myHref);
//      }
//      ByteRange br = brc.getByteRange();
//      anchors.add(myHref = new Anchor.Href(br.begin(), br.end(), "none"));
//      if (saveBBOX != null) {
//        DviRect r = saveBBOX;
//        repaint(r.x(), r.y(), r.width(), r.height());
//      }
//      DviRect r = brc.getBounds().shrink(res.shrinkFactor());
//      repaint(r.x(), r.y(), r.width(), r.height());
//      saveBBOX = r;
//      return br;
//    } catch (DviException ex) {
//      Logger.trace(ex);
//      return null;
//    }
//  }

  private DviRect displayRect = null;

  public void determineSize()
  {
    final DviToolkit utils = getDviContext().getDviToolkit();
    displayRect = null;
    try {
      if (layout != null)
        displayRect = layout.determinePageSize(this);
    } catch (Exception e) {
      LOGGER.severe(e.toString());
      e.printStackTrace();
    }
    if (displayRect == null) {
      displayRect = utils.computePageBoundingBox
        (viewSpec.getPaperSize(), viewSpec.getResolution());
    }

    setPreferredSize(new Dimension(displayRect.width(), displayRect.height()));
    setSize(new Dimension(displayRect.width(), displayRect.height()));
    revalidate();
  }
  
  private static ExecutorService exe = Executors.newFixedThreadPool(3, new DaemonThreadFactory());
  
  protected ExecutorService getExecutorService()
  {
    return exe;
  }
  
  protected void startPagePreparation(Graphics g, final Rectangle rect, final DviPage page, final ViewSpec viewSpec)
  {
    final DviToolkit utils = getDviContext().getDviToolkit();

    LOGGER.finer("Preparing page for paint: " + page);
    getExecutorService().submit(new Runnable() {
      public void run()
      {
        try {
          utils.prepareForRendering(page, viewSpec);
        } catch (Exception e) {
          LOGGER.warning(e.toString());
        }
        SwingUtilities.invokeLater(new Runnable() {
          public void run()
          {
            setBusy(false);
            repaint();
          }
        });
      }
    });
    setBusy(true);
    paintDummyContents(g, rect, viewSpec);
  }

  private void paintDummyContents(Graphics g, final Rectangle rect,
      final ViewSpec viewSpec)
  {
    g.setColor(viewSpec.getBackgroundColor().toColor());
    g.fillRect(rect.x, rect.y, rect.width, rect.height);
  }
  
  protected void paintComponent(Graphics g)
  {
    super.paintComponent(g);

    final DviToolkit utils = getDviContext().getDviToolkit();
    
    if (page == null) return;
    
    final Rectangle rect = g.getClipBounds();
    g.fillRect(rect.x, rect.y, rect.width, rect.height);

    boolean enableDelayedRendering = true;
    
    try {
      enableDelayedRendering = getEnableDelayedRendering();
    } catch (DviException e1) {
      LOGGER.warning(e1.toString());
    }
    
    if (enableDelayedRendering) {
      if (!utils.canRenderPageImmediately(page, viewSpec)) {
        LOGGER.finest("delayed rendering");
        startPagePreparation(g, rect, page, viewSpec);
        return;
      }
    }
    
    try {
      LOGGER.finest("rendering page=" + page.getPageNumber() + " rect=" + rect + " viewSpec=" + viewSpec);
//      DviRect targetArea = new DviRect
//        (rect.x + displayRect.x(), rect.y + displayRect.y(), rect.width+1, rect.height+1);
      DviRect targetArea = DviRect.fromRectangle(rect).translate(displayRect.topLeft());
      if (!targetArea.isEmpty()) {
          BufferedImage img = utils.renderToBufferedImage(page, targetArea, viewSpec);
          g.drawImage(img, rect.x, rect.y, null);
      }
    } catch (DviException e) {
      LOGGER.warning(e.toString());
      e.printStackTrace();
      paintDummyContents(g, rect, viewSpec);
    }
  }
  
  public void stateChanged(ChangeEvent e)
  {
    LOGGER.fine("stateChanged called: EDT=" + SwingUtilities.isEventDispatchThread());
    SwingUtilities.invokeLater(new Runnable() {
      public void run()
      {
        determineSize();
        revalidate();
        repaint();
      }
    });
  }
  
  private DviLayoutManager layout = new DefaultDviLayoutManager(this, 1);
  
  public boolean getEnableDelayedRendering()
  throws DviException
  {
    if (layout == null) return true;
    return layout.getEnableDelayedRendering(this);
  }

  public DviLayoutManager getDviLayout()
  {
    return layout;
  }

  public void setDviLayout(DviLayoutManager layout)
  {
    if (layout == null)
      throw new NullPointerException("layout");
    this.layout = layout;
  }

  protected void setBusy(boolean busy) {
    this.busy = busy;
    fireChangeEvent();
  }

  public boolean isBusy() {
    return busy;
  }
  
  protected EventListenerList listenerList = new EventListenerList();

  public void addChangeListener(ChangeListener l)
  {
      listenerList.add(ChangeListener.class, l);
  }

  public void removeChangeListener(ChangeListener l)
  {
      listenerList.remove(ChangeListener.class, l);
  }

  protected void fireChangeEvent()
  {
    ChangeEvent event = null;
    
    Object[] listeners = listenerList.getListenerList();
    for (int i = listeners.length-2; i>=0; i-=2) {
      if (listeners[i] == ChangeListener.class) {
        if (event == null)
          event = new ChangeEvent(this);
        ((ChangeListener)listeners[i+1]).stateChanged(event);
      }
    }
  }

}
