package com.gmail.dpierron.calibre.opds;
/**
 *
 */

import com.gmail.dpierron.calibre.cache.CachedFile;
import com.gmail.dpierron.calibre.cache.CachedFileManager;
import com.gmail.dpierron.calibre.configuration.ConfigurationManager;
import com.gmail.dpierron.calibre.datamodel.Book;
import com.gmail.dpierron.tools.i18n.Localization;
import com.gmail.dpierron.tools.Helper;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.*;


public abstract class ImageManager {
  private final static Logger logger = LogManager.getLogger(ImageManager.class);

  // private Map<File, File> generatedImages;

  private int imageHeight = 1;
  private boolean imageSizeChanged;     // Set true to force regeneration of images
  private long timeInImages;
  private static int countOfImagesGenerated;

  abstract String getResizedFilename();
  abstract String getResizedFilenameOld(Book book);
  abstract String getDefaultResizedFilename();
  abstract String getImageHeightDat();

  // CONSTRUCTOR

  ImageManager(int imageHeight) {
    reset();
    this.imageHeight = imageHeight;

    CachedFile imageSizeFile = CachedFileManager.addCachedFile(ConfigurationManager.getCurrentProfile().getDatabaseFolder(), getImageHeightDat());
    FeedHelper.checkFileNameIsNewStandard(imageSizeFile, CachedFileManager.addCachedFile(ConfigurationManager.getCurrentProfile().getDatabaseFolder(), imageSizeFile.getName().substring(4)));

    imageSizeChanged = true;      // Assume true if sizefile does not exist
    if (imageSizeFile.exists()) {
      try {
        BufferedReader in = null;
        try {
          in = new BufferedReader(new FileReader(imageSizeFile));
          String line = in.readLine();      // Skip the comment line
          line = in.readLine();
          int oldSize = Integer.valueOf(line);
          imageSizeChanged = oldSize != imageHeight;
        } finally {
          if (in != null) {
            in.close();
          }
        }
      } catch (Exception e) {
        // we don't care about errors, let's just say size has changed
        if (logger.isDebugEnabled())
          logger.debug("Failed to read size from file " + imageSizeFile);
        imageSizeChanged = true;
      } finally {
        // #c2o-238
        // If the image size has changed delete the height file in case generate goes wrong!
        // It will be recreated if we successfully complete the run.  We do not need to delete
        // it if the image size is unchanged as in this case exiting image files are correct size.
        if (imageSizeChanged) {
          imageSizeFile.delete();
          CachedFileManager.removeCachedFile(imageSizeFile);
        }
      }
    }
  }

  // METHODS and PROPERTIES

  public void reset () {
    // generatedImages = new HashMap<File, File>();
    countOfImagesGenerated = 0;
    timeInImages = 0;
  }

  public final static ThumbnailManager newThumbnailManager() {
    return new ThumbnailManager(ConfigurationManager.getCurrentProfile().getThumbnailHeight());
  }

  public final static ImageManager newCoverManager() {
    return new CoverManager(ConfigurationManager.getCurrentProfile().getCoverHeight());
  }

  /*
  public void setImageToGenerate(CachedFile resizedCoverFile, CachedFile coverFile) {
    if (!generatedImages.containsKey(resizedCoverFile)) {
      generateImage(coverFile,resizedCoverFile);
      generatedImages.put(resizedCoverFile, coverFile);
    }
  }
  */

  boolean hasImageSizeChanged() {
    return imageSizeChanged;
  }

  /**
   * Get the URI for a cover image
   * (on the assumption it is in the library)
   *
   * @param book
   * @return
   */
  String getImageUri(Book book) {
    String uriBase = ConfigurationManager.getCurrentProfile().getUrlBooks();
    if (Helper.isNullOrEmpty(uriBase)) {
      uriBase = Constants.LIBRARY_PATH_PREFIX;
    }
    return uriBase + FeedHelper.urlEncode(book.getPath() + "/" + getResizedFilename(), true);
  }

  /**
   *  Save the height of the images that we havfe generated.
   *
   *  For efficency reasons, this method is expected to be called once
   *  after a particular catalog generation run has completed
   */
  public void writeImageHeightFile() {
    File imageSizeFile = new File(ConfigurationManager.getCurrentProfile().getDatabaseFolder(), getImageHeightDat());
    try {
      PrintWriter out = null;
      try {
        out = new PrintWriter(new FileWriter(imageSizeFile));
        out.println("## Automatically generated config file.  DO NOT EDIT!");
        out.println(Integer.toString(imageHeight));
      } finally {
        if (out != null)
          out.close();
      }
    } catch (IOException e) {
      // we don't care if the image height file cannot be written, image will be recomputed and that's all
    }
    CachedFileManager.addCachedFile(imageSizeFile).clearCachedInformation();
  }

  /**
   * Get the contents of the specified file as base64 string
   * so that we can embed the image in the xml/html file
   *
   * @return
   */
  public String getFileToBase64Uri (File f) {
    final String base64code = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "+/";

    int length = (int)f.length();
    int paddingCount = (3 - (length % 3)) % 3;

    byte[] data = new byte[length + 2];
    // Preset potential padding bytes to null
    for (int i = 0 ; i < 2; i++) {
      data[length+(i)] = 0;
    }
    try {
      DataInputStream in = new DataInputStream(new FileInputStream(f));
      in.readFully(data,0,length);
      in.close();
    } catch (EOFException eof) {
      // Ignore this error as we got all the data!
      // However this is still unexpected so lets log it when tracing
      if (logger.isTraceEnabled())  logger.trace("Unexpected EOF reading " + f);
    } catch (IOException e) {
      // Errors are unexpected - set dummy URI if that is the case
      logger.warn("Unable to embed cover file " + f.getAbsolutePath() + "(IO Exception " + e.getMessage() + ")"); Helper.statsWarnings++;
      return Constants.PARENT_PATH_PREFIX + Constants.DEFAULT_IMAGE_FILENAME;
    }
    // process 3 bytes at a time, churning out 4 output bytes
    StringBuffer encoded = new StringBuffer("");
    for (int i = 0; i < length; i += 3) {
      int j = ((data[i] & 0xff) << 16) +
              ((data[i + 1] & 0xff) << 8) +
               (data[i + 2] & 0xff);
      encoded = encoded.append("" + base64code.charAt((j >> 18) & 0x3f) +
                               "" + base64code.charAt((j >> 12) & 0x3f) +
                               "" + base64code.charAt((j >> 6) & 0x3f) +
                               "" + base64code.charAt(j & 0x3f));
    }
    data = null;        // <ay not be necessary - but lets help garbage collector
    // Add final padding characters
    for (int i = paddingCount ; i > 0 ; i--) {
      encoded.setCharAt(encoded.length() - i, '=');
    }
    return "data:image/"
            // take image type from file extenson
            + f.getName().substring(f.getName().lastIndexOf('.')+1) + ";base64,"
            + encoded.toString();
  }

  /**
   *    * generate a single image file
   *
   * @param imageFile   File for written fir output image
   * @param coverFile   File to be read for input image
   */
  public void generateImage(CachedFile imageFile, CachedFile coverFile) {
    assert imageFile != null ;
    assert coverFile != null && coverFile.exists();
    logger.debug("generateImage: " + imageFile.getAbsolutePath());
    long now = System.currentTimeMillis();
    try {
      ImageFile ct = new ImageFile(coverFile.getAbsolutePath());
      // If the image failed to load -  no point in trying to generate it!
      if (! ct.isImageLoaded()) return;

      ct.getImage(imageHeight, ImageFile.VERTICAL);
      ct.saveImage(imageFile, ImageFile.IMAGE_JPEG);
      // bug #732821 Ensure file added to those cached for copying
      CachedFile cf = CachedFileManager.addCachedFile(imageFile);
      if (logger.isTraceEnabled())
        logger.trace("generateImages: added new thumbnail file " + imageFile.getAbsolutePath() + " to list of files to copy");
      countOfImagesGenerated++;         // Update count of files processed
    } catch (Exception e) {
      CatalogManager.callback.errorOccured(Localization.Main.getText("error.generatingImage", imageFile.getAbsolutePath()), e);
      if (logger.isTraceEnabled())  logger.trace(e);
    } catch (Throwable t) {
         logger.warn("Unexpected error trying to generate image " + coverFile.getAbsolutePath() + "\n" + t ); Helper.statsWarnings++;
    }
    timeInImages += (System.currentTimeMillis() - now);
    imageFile.clearCachedInformation();
  }

  public long getTimeInImages() {
    return timeInImages;
  }

  public int getCountOfImagesGenerated() {
    return countOfImagesGenerated;
  }

}
