/*
 * 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.font;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.logging.Logger;

import dvi.DviException;
import dvi.api.CharacterCodeMapper;
import dvi.api.DviContextSupport;
import dvi.api.DviFont;
import dvi.ctx.BakomaUnicodeCharacterCodeMapper;
import dvi.ctx.Type1DefaultCharacterCodeMapper;
import dvi.util.concurrent.CacheEntry;
import dvi.util.concurrent.CachedComputer;
import dvi.util.concurrent.ThreadedComputer;
import dvi.util.progress.ProgressItem;

public class DviFontResolver
extends AbstractDviFontResolver
{
  private static final Logger LOGGER = Logger
      .getLogger(DviFontResolver.class.getName());

  // TODO: set maximum number of fonts to cache.
  // TODO: outsource the size configurations.
  private static final CachedComputer<String, Collection<DviFont>> dviFontComputer
  = new CachedComputer<String, Collection<DviFont>>
    (new ThreadedComputer<String, Collection<DviFont>>(3)) {
    @Override
    protected boolean removeEldestEntry(Map.Entry<String, CacheEntry<String, Collection<DviFont>>> entry)
    {
      boolean remove = getCache().size() > 64;
      return remove;
    }
  };

  private boolean trueTypeEnabled = false;

  private boolean type1Enabled = false;

  private boolean openTypeEnabled = false;

  private boolean pkEnabled = true;
  
  private static final CharacterCodeMapper defaultTrueTypeMapper = new BakomaUnicodeCharacterCodeMapper();
  private static final CharacterCodeMapper defaultType1Mapper
    = new Type1DefaultCharacterCodeMapper(Type1DefaultCharacterCodeMapper.ENC_OT1);

  public DviFontResolver(DviContextSupport dcs, LogicalFont logicalFont) {
    super(dcs, logicalFont);
  }

  @Override
  public Collection<DviFont> call() throws Exception
  {
    ProgressItem progress = getDviContext().getProgressRecorder().open(
        "resolving " + getLogicalFont().fontSpec().name());
    try {
      LOGGER.fine("Resolving DVI font " + getLogicalFont());
      LogicalFont mappedLogicalFont = getDviContext().mapLogicalFont(
          getLogicalFont());
      LOGGER.fine("Mapped logical font is " + mappedLogicalFont);
      List<DviFont> list = new ArrayList<DviFont>();
      {
        // Note: We have to use the original logical font to find a virtual font.
        {
          Future<Collection<DviFont>> future = dviFontComputer
            .compute(new VirtualFontResolver(this, getLogicalFont()));
          list.addAll(future.get());
        }

        if (list.size() == 0 && openTypeEnabled()) {
          TrueTypeFontResolver resolver = new TrueTypeFontResolver(this, mappedLogicalFont);
          resolver.setFileExtension(".otf");
          Future<Collection<DviFont>> future = dviFontComputer.compute(resolver);
          Collection<DviFont> fonts = future.get();
          list.addAll(fonts);
        }
        if (list.size() == 0 && type1Enabled) {
          Future<Collection<DviFont>> future = dviFontComputer
          .compute(new Type1FontResolver(this, mappedLogicalFont));
          Collection<DviFont> fonts = future.get();
          for (DviFont font : fonts) {
            if (font instanceof AWTDynamicPkFont) {
              ((AWTDynamicPkFont) font).setCharacterCodeMapper
                (defaultType1Mapper);
            }
            list.add(font);
          }
        }
        if (list.size() == 0 && trueTypeEnabled) {
          Future<Collection<DviFont>> future = dviFontComputer
          .compute(new TrueTypeFontResolver(this, mappedLogicalFont));
          Collection<DviFont> fonts = future.get();
          for (DviFont font : fonts) {
            if (font instanceof AWTDynamicPkFont) {
              ((AWTDynamicPkFont) font).setCharacterCodeMapper
                (defaultTrueTypeMapper);
            }
            list.add(font);
          }
        }

        if (list.size() == 0 && pkEnabled() ) {
          Future<Collection<DviFont>> future = dviFontComputer
          .compute(new PkFontResolver(this, mappedLogicalFont));
          list.addAll(future.get());
        }
        
        if (list.size() == 0) {
          Future<Collection<DviFont>> future = dviFontComputer
            .compute(new AWTDynamicPkFontResolver(this, mappedLogicalFont));
          list.addAll(future.get());
        }
      }
      return list;
    } finally {
      progress.close();
    }
  }
  
  @Override
  public String getCacheKey()
  {
    LogicalFont spec = getLogicalFont();
    return spec.fontSpec().fontName()
    + "-" + spec.fontSpec().checkSum()
    + "-" + spec.fontSpec().spaceSize()
    + "-" + spec.fontSpec().designSize()
    + "-" + spec.resolution().dpi()
    + "-" + spec.dviUnit()
    ;
  }

  @Override
  protected DviFont createInstanceFromStream(InputStream openStream)
      throws DviException
  {
    throw new UnsupportedOperationException();
  }

  @Override
  protected String mapToDviResourceName(LogicalFont logicalFont)
  {
    throw new UnsupportedOperationException();
  }

  public void setOpenTypeEnabled(boolean openTypeEnabled) {
    this.openTypeEnabled = openTypeEnabled;
  }

  public boolean openTypeEnabled() {
    return openTypeEnabled;
  }

  public void setPkEnabled(boolean pkEnabled) {
    this.pkEnabled = pkEnabled;
  }

  public boolean pkEnabled() {
    return pkEnabled;
  }
}
