/*
 * $Id: TileSlicerImpl.java 8009 2011-11-10 21:27:52Z uckelman $
 *
 * Copyright (c) 2010, 2011 by Joel Uckelman
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License (LGPL) as published by the Free Software Foundation.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, copies are available
 * at http://www.opensource.org.
 */

package VASSAL.tools.image.tilecache;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

import VASSAL.tools.image.GeneralFilter;
import VASSAL.tools.lang.Callback;

/**
 * Slices an image into tiles.
 *
 * @since 3.2.0
 * @author Joel Uckelman
 */
public class TileSlicerImpl implements TileSlicer {
  /**
   * Slices an image into tiles.
   *
   * @param src the source image
   * @param iname the basename for the tiles
   * @param tpath the path for the tiles
   * @param tw the tile width
   * @param th the tile height
   * @param exec the executor in which to run tasks
   * @param progress a callback for indicating progress
   */
  public void slice(
    BufferedImage src,
    String iname,
    String tpath,
    int tw,
    int th,
    ExecutorService exec,
    Callback<Void> progress
  ) throws IOException
  {
    final int sw = src.getWidth();
    final int sh = src.getHeight();

    final List<Future<Void>> futures = new ArrayList<Future<Void>>();

    // slice unscaled 1:1 tiles
    final TaskMaker unscaled = new TaskMaker() {
      public TileTask make(BufferedImage src, File f,
                           int tx, int ty, int tw, int th, int sw, int sh) {
        return new TileTask(src, f, tx, ty, tw, th, sw, sh);
      }
    };

    queueTileTasks(
      src, iname, tpath, 1, tw, th, sw, sh, unscaled, exec, futures
    );

    // slice scaled tiles, starting at 1:2
    final TaskMaker scaled = new TaskMaker() {
      private final GeneralFilter.Filter filter =
        new GeneralFilter.Lanczos3Filter();

      public TileTask make(BufferedImage src, File f,
                           int tx, int ty, int tw, int th, int dw, int dh) {
        return new ScaledTileTask(src, f, filter, tx, ty, tw, th, dw, dh);
      }
    };

    for (int div = 2; sw/div > 0 && sh/div > 0; div <<= 1) {
      final int dw = sw/div;
      final int dh = sh/div;

      queueTileTasks(
        src, iname, tpath, div, tw, th, dw, dh, scaled, exec, futures
      );
    }

    // wait for all tiles to complete
    try {
      for (Future<Void> f : futures) {
        f.get();
        progress.receive(null);
      }
    }
    catch (CancellationException e) {
      // should never happen
      throw new IllegalStateException(e);
    }
    catch (ExecutionException e) {
      throw (IOException) new IOException().initCause(e);
    }
    catch (InterruptedException e) {
      // should never happen
      throw new IllegalStateException(e);
    }
    finally {
      // cancel everything if anything fails
      for (Future<Void> f : futures) {
        if (!f.isDone()) f.cancel(true);
      }
    }
  }

  protected static interface TaskMaker {
    public TileTask make(BufferedImage src, File f,
                         int tx, int ty, int tw, int th, int dw, int dh);
  }

  protected static void queueTileTasks(
    BufferedImage src,
    String iname,
    String tpath,
    int div,
    int tw,
    int th,
    int dw,
    int dh,
    TaskMaker tm,
    ExecutorService exec,
    List<Future<Void>> futures
  )
  {
    final int tcols = (int) Math.ceil((double) dw / tw);
    final int trows = (int) Math.ceil((double) dh / th);

    for (int tx = 0; tx < tcols; ++tx) {
      for (int ty = 0; ty < trows; ++ty) {
        final String tn = TileUtils.tileName(iname, tx, ty, div);
        final File f = new File(tpath, tn);

        final TileTask tt = tm.make(src, f, tx, ty, tw, th, dw, dh);
        futures.add(exec.submit(tt));
      }
    }
  }
}