/*
 * Copyright (c) 2007 by Michael Kiefte
 *
 * 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.imports;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;

import VASSAL.build.Buildable;
import VASSAL.build.GameModule;
import VASSAL.build.module.Map;
import VASSAL.build.module.map.LayeredPieceCollection;
import VASSAL.build.widget.PieceSlot;
import VASSAL.tools.ArchiveWriter;

/**
 * Abstract parent of all importer classes.
 * 
 * @author Michael Kiefte
 * @since 3.1.0
 */
public abstract class Importer {
  
  /*
   * We need this for the getCaseInsensitiveFile method.
   */
  protected ImportAction action;
  protected File file;
  
  static protected void insertComponent(Buildable child, Buildable parent) {
    child.build(null);
    if (child instanceof PieceSlot)
      ((PieceSlot) child).updateGpId(GameModule.getGameModule());
    child.addTo(parent);
    parent.add(child);
  }
  
  /**
   * @return The VASSAL Map object corresponding to this imported map.
   */
  protected Map getMainMap() {
    return GameModule.getGameModule().getAllDescendantComponentsOf(Map.class).iterator().next();
  }
  
  protected LayeredPieceCollection getLayeredPieceCollection() {
    final Map map = getMainMap();
    final List<LayeredPieceCollection> l =
      map.getComponentsOf(LayeredPieceCollection.class);

    LayeredPieceCollection collection = null;
    if (l.size() == 0) {
      collection = new LayeredPieceCollection();
      insertComponent(collection, map);
      collection.setAttribute(LayeredPieceCollection.PROPERTY_NAME, "Layer");
    }
    else {
      assert(l.size() == 1);
      collection = l.get(0);
    }
    return collection;
  }
  
  /**
   * The method that actually loads the file and creates the classes containing information needed for the archive. 
   * The archive is written in <code>writeToArchive</code>.
   * 
   * @param f            The base file to be imported.
   * @throws IOException
   */
  protected void load(File f) throws IOException {
    this.file = f;
  }
  
  /**
   * Create the VASSAL module based on the classes created by <code>load</code>. This should not be called directly
   * but rather <code>importFile</code>.
   * 
   * @throws IOException
   */
  public abstract void writeToArchive() throws IOException;
  
  /**
   * Two methods are needed to import a file. <code>Importer.importFile</code> initializes the game module
   * and calls <code>load</code> which must be overridden by descendents.
   * 
   * @param action       <code>ImportAction</code> which creates the <code>Importer</code>. This is needed for
   *                     file dialogs that may be called by <code>Importer</code> methods.
   * @param f            The base file to be imported.
   * @throws IOException
   */
  public void importFile(ImportAction action, File f) throws IOException {
    this.action = action;
    load(f);    
  }

  /**
   * Return a file name without the extension.
   */
  public static String stripExtension(String s) {
    if (s.equals(".") || s.equals(".."))
      return s;
    final int index = s.lastIndexOf('.');
    final int pathIdx = s.lastIndexOf(File.separatorChar);
    if (index == -1 || index < pathIdx)
      return s;
    else
      return s.substring(0, index);
  }

  /**
   * Read a null-terminated string representing a Windows file name and convert
   * Windows separator characters <tt>'\\'</tt> to the local separator character.
   * This is the default file name format for many imported modules and should be used
   * whenever a filename is read as a null-terminated string in order to ensure
   * platform independence.
   */
  public static String readWindowsFileName(InputStream in) throws IOException {
    final StringBuilder sb = new StringBuilder();
    char ch;
    do {
      ch = (char) in.read();
      if (ch == '\\')
        sb.append(File.separatorChar);
      else if (ch != 0)
        sb.append(ch);
    } while (ch != 0);
    return sb.toString();
  }

  /**
   * Read a null-terminated string from a file up to a maximum length which includes
   * the null termination. If the actual string is longer, no more bytes will be read.
   */
  public static String readNullTerminatedString(InputStream in, int maxLen) throws IOException {
    final StringBuilder sb;
    if (maxLen == 0)
      sb = new StringBuilder();
    else
      sb = new StringBuilder(maxLen);
    char ch;
    for (int i = 0; maxLen == 0 || i < maxLen; ++i) {
      ch = (char) in.read();
      if (ch >= 0x20 && ch <= 0x7e)
        sb.append(ch);
      else if (ch != 0)
        sb.append(" ");
      else
        break;
    }
    return sb.toString();
  }

  /**
   * Return a null-terminated string from an input stream.
   */
  public static String readNullTerminatedString(InputStream in) throws IOException {
    return Importer.readNullTerminatedString(in, 0);
  }

  /**
   * Get a unique file name for an image in the game module archive. This function
   * tests the provided name against those that are already present in the archive
   * and if the file name already exists, creates an alternate, unique file name.
   * If an alternate is created, it is of the form <code><tt>name</tt> + "(<tt>n</tt>)"</code>
   * where <tt>n</tt> is an integer.
   */
  public static String getUniqueImageFileName(String s, String ext) {
    String t = s;
    int index = 0;
    final ArchiveWriter writer = GameModule.getGameModule().getArchiveWriter();
    while (writer.isImageAdded(writer.getImagePrefix() + t + ext))
      t = s + '(' + (++index) + ')';
    return t + ext;
  }
  
  public static String getUniqueImageFileName(String s) {
    return getUniqueImageFileName(s, ".png");
  }

  /**
   * Get the file name without the qualifying path.
   */
  public static String getFileName(String s) {
    if (s.equals(".") || s.equals(".."))
      return s;
    final int pathIdx = s.lastIndexOf(File.separatorChar);
    return s.substring(pathIdx + 1, s.length());
  }

  /**
   * Get the extension from a file name.
   */
  public static String getExtension(String s) {
    if (s.equals(".") || s.equals(".."))
      return "";
    final int extIdx = s.lastIndexOf('.');
    final int pathIdx = s.lastIndexOf(File.separatorChar);
    if (extIdx == -1 || extIdx < pathIdx || extIdx >= s.length() - 1)
      return "";
    else
      return s.substring(extIdx + 1);
  }

  /**
   * Strip the extension from a filename and replace with the given extension.
   */
  public static String forceExtension(String s, String ext) {
    if (s.equals(".") || s.equals(".."))
      return s;
    return Importer.stripExtension(s) + '.' + ext;
  }

  /**
   * Determine whether the file is valid for the given importer.
   * @param f
   * @return <code>true</code> if <code>f</code> is a valid file for this type.
   * @throws FileNotFoundException 
   * @throws IOException 
   */
  public abstract boolean isValidImportFile(File f) throws IOException;
}
