/*
 * 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.browser.v2.list;

import java.awt.Component;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.imageio.ImageIO;
import javax.swing.Icon;

import dvi.DviException;
import dvi.DviObject;
import dvi.DviRect;
import dvi.api.DviContextSupport;
import dvi.api.DviPage;
import dvi.ctx.DviToolkit;
import dvi.gui.swing.ViewSpec;
import dvi.util.DviUtils;
import dvi.v2.cache.AbstractCacheCostHandler;
import dvi.v2.cache.CacheEntryTransform;
import dvi.v2.cache.GlueCacheHandler;
import dvi.v2.cache.MemoryCache;

public class DviPageIcon
extends DviObject
implements Icon
{
  private static final Logger LOGGER = Logger.getLogger(DviPageIcon.class.getName());
  
  private static interface Data {
    BufferedImage toImage();
  }
  
  private static final class MemoryData
  implements Data
  {
    private final BufferedImage image;

    public MemoryData(BufferedImage image)
    {
      this.image = image;
    }

    public BufferedImage toImage() {
      return image;
    }
  }

  private static final class CompressedMemoryData
  implements Data
  {
    byte [] buf;
    
    public CompressedMemoryData(BufferedImage image)
    {
      try {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        ImageIO.write(image, "jpg", os);
        os.close();
        buf = os.toByteArray();
      } catch (IOException e) {
        DviUtils.logStackTrace(LOGGER, Level.WARNING, e);
      }
    }
    
    public BufferedImage toImage() {
      ByteArrayInputStream is = new ByteArrayInputStream(buf);
      try {
        BufferedImage image = ImageIO.read(is);
        is.close();
        return image;
      } catch (IOException e) {
        DviUtils.logStackTrace(LOGGER, Level.WARNING, e);
      }
      return null;
    }
  }
  
  private static final AbstractCacheCostHandler<String, MemoryData> costHandler
    = new AbstractCacheCostHandler<String, MemoryData>(32000000L) {
      @Override
      protected long computeItemSize(String key, MemoryData value) {
        return value.image.getWidth() * value.image.getHeight() * 4;
      }
  };
  private static final AbstractCacheCostHandler<String, CompressedMemoryData> costHandler2
  = new AbstractCacheCostHandler<String, CompressedMemoryData>(32000000L) {
    @Override
    protected long computeItemSize(String key, CompressedMemoryData value) {
      return value.buf.length;
    }
  };
  
  private static final CacheEntryTransform<String, MemoryData, CompressedMemoryData>
    transform = new CacheEntryTransform<String, MemoryData, CompressedMemoryData>() {
      public MemoryData mapBackward(String key, CompressedMemoryData v2) {
        if (v2 == null) return null;
        return new MemoryData(v2.toImage());
      }

      public CompressedMemoryData mapForward(String key, MemoryData v1) {
        if (v1 == null) return null;
        return new CompressedMemoryData(v1.image);
      }
  };
  
  private static final MemoryCache<String, CompressedMemoryData> l2cache =
      new MemoryCache<String, CompressedMemoryData>(costHandler2, null);
  private static final MemoryCache<String, MemoryData> cache = new MemoryCache<String, MemoryData>
    (costHandler, new GlueCacheHandler<String, MemoryData, CompressedMemoryData>(l2cache, transform));

  private final DviPage page;
  private final DviRect bbox;
  private final ViewSpec viewSpec;

  public DviPageIcon(DviContextSupport dcs, DviPage page, DviRect bbox, ViewSpec viewSpec) {
    super(dcs);
    this.page = page;
    this.bbox = bbox;
    this.viewSpec = viewSpec;
  }

  public int getIconHeight() {
    return bbox.height();
  }

  public int getIconWidth() {
    return bbox.width();
  }

  public void paintIcon(Component c, Graphics g, int x, int y) {
    String key = page.getCacheKey();
    MemoryData data = cache.get(key);
    if (data == null) {
      data = createData();
      if (data != null) {
        cache.put(key, data);
      }
    }
    
    if (data != null) {
      Image image = data.toImage();
      g.drawImage(image, x, y, null);
    }
    
  }

  private MemoryData createData() {
    if (page == null) return null;
    DviToolkit toolkit = getDviContext().getDviToolkit();
    try {
      BufferedImage img = toolkit.renderToBufferedImage(page, getBoundingBox(), getViewSpec());
      MemoryData data = new MemoryData(img);
      return data;
    } catch (DviException e) {
      DviUtils.logStackTrace(LOGGER, Level.WARNING, e);
    }
    return null;
  }

  public DviPage getPage() {
    return page;
  }

  public DviRect getBoundingBox() {
    return bbox;
  }

  public ViewSpec getViewSpec() {
    return viewSpec;
  }
}
