/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */

/*
  Part of the Processing project - http://processing.org

  Copyright (c) 2013-16 The Processing Foundation
  Copyright (c) 2011-12 Ben Fry and Casey Reas

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License version 2
  as published by the Free Software Foundation.

  This program 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 General Public License for more details.

  You should have received a copy of the GNU General Public License along
  with this program; if not, write to the Free Software Foundation, Inc.
  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

package processing.app.contrib;

import java.io.*;

import processing.app.Base;
import processing.app.Language;
import processing.app.Platform;
import processing.app.Util;
import processing.core.PApplet;
import processing.data.StringDict;
import processing.data.StringList;


/**
 * A class to hold information about a Contribution that can be downloaded.
 */
public class AvailableContribution extends Contribution {
  protected final ContributionType type;   // Library, tool, etc.
  protected final String link;             // Direct link to download the file


  public AvailableContribution(ContributionType type, StringDict params) {
    this.type = type;
    this.link = params.get("download");

    categories = parseCategories(params);
    imports = parseImports(params);
    name = params.get("name");
    authors = params.get("authors");
//    if (authors == null) {
//      authors = params.get("authorList");
//    }
    url = params.get("url");
    sentence = params.get("sentence");
    paragraph = params.get("paragraph");

    String versionStr = params.get("version");
    if (versionStr != null) {
      version = PApplet.parseInt(versionStr, 0);
    }

    setPrettyVersion(params.get("prettyVersion"));

    String lastUpdatedStr = params.get("lastUpdated");
    if (lastUpdatedStr != null) {
      try {
        lastUpdated =  Long.parseLong(lastUpdatedStr);
      } catch (NumberFormatException e) {
        lastUpdated = 0;
      }
    }
    String minRev = params.get("minRevision");
    if (minRev != null) {
      minRevision = PApplet.parseInt(minRev, 0);
    }

    String maxRev = params.get("maxRevision");
    if (maxRev != null) {
      maxRevision = PApplet.parseInt(maxRev, 0);
    }
  }


  /**
   * @param contribArchive
   *          a zip file containing the library to install
   * @param confirmReplace
   *          true to open a dialog asking the user to confirm removing/moving
   *          the library when a library by the same name already exists
   * @param status
   *          the StatusPanel. Pass null if this function is called for an
   *          install-on-startup
   * @return
   */
  public LocalContribution install(Base base, File contribArchive,
                                   boolean confirmReplace, StatusPanel status) {
    // Unzip the file into the modes, tools, or libraries folder inside the
    // sketchbook. Unzipping to /tmp is problematic because it may be on
    // another file system, so move/rename operations will break.
//    File sketchbookContribFolder = type.getSketchbookFolder();
    File tempFolder = null;

    try {
      tempFolder = type.createTempFolder();
    } catch (IOException e) {
      if (status != null)
        status.setErrorMessage(Language.text("contrib.errors.temporary_directory"));
      return null;
    }
    Util.unzip(contribArchive, tempFolder);
//    System.out.println("temp folder is " + tempFolder);
//    Base.openFolder(tempFolder);

    // Now go looking for a legit contrib inside what's been unpacked.
    File contribFolder = null;

    // Sometimes contrib authors place all their folders in the base directory
    // of the .zip file instead of in single folder as the guidelines suggest.
    if (type.isCandidate(tempFolder)) {
      /*
      // Can't just rename the temp folder, because a contrib with this name
      // may already exist. Instead, create a new temp folder, and rename the
      // old one to be the correct folder.
      File enclosingFolder = null;
      try {
        enclosingFolder = Base.createTempFolder(type.toString(), "tmp", sketchbookContribFolder);
      } catch (IOException e) {
        status.setErrorMessage("Could not create a secondary folder to install.");
        return null;
      }
      contribFolder = new File(enclosingFolder, getName());
      tempFolder.renameTo(contribFolder);
      tempFolder = enclosingFolder;
      */
      if (status != null) {
        status.setErrorMessage(Language.interpolate("contrib.errors.needs_repackage", getName(), type.getTitle()));
      }
      return null;
    }

//    if (contribFolder == null) {
    // Find the first legitimate looking folder in what we just unzipped
    contribFolder = type.findCandidate(tempFolder);
//    }
    LocalContribution installedContrib = null;

    if (contribFolder == null) {
      if (status != null) {
        status.setErrorMessage(Language.interpolate("contrib.errors.no_contribution_found", type));
      }

    } else {
      File propFile = new File(contribFolder, type + ".properties");
      if (writePropertiesFile(propFile)) {
        // 1. contribFolder now has a legit contribution, load it to get info.
        LocalContribution newContrib = type.load(base, contribFolder);

        // 1.1. get info we need to delete the newContrib folder later
        File newContribFolder = newContrib.getFolder();

        // 2. Check to make sure nothing has the same name already,
        // backup old if needed, then move things into place and reload.
        installedContrib =
          newContrib.copyAndLoad(base, confirmReplace, status);

        // Restart no longer needed. Yay!
//        if (newContrib != null && type.requiresRestart()) {
//          installedContrib.setRestartFlag();
//          //status.setMessage("Restart Processing to finish the installation.");
//        }

        // 3.1 Unlock all the jars if it is a mode or tool
        if (newContrib.getType() == ContributionType.MODE) {
          ((ModeContribution) newContrib).clearClassLoader(base);
        }
        else if (newContrib.getType() == ContributionType.TOOL) {
          ((ToolContribution) newContrib).clearClassLoader();
        }

        // 3.2 Delete the newContrib, do a garbage collection, hope and pray
        // that Java will unlock the temp folder on Windows now
        newContrib = null;
        System.gc();


        if (Platform.isWindows()) {
          // we'll even give it a second to finish up ... because file ops are
          // just that flaky on Windows.
          try {
            Thread.sleep(1000);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }

        // 4. Okay, now actually delete that temp folder
        Util.removeDir(newContribFolder, false);

      } else {
        if (status != null) {
          status.setErrorMessage(Language.text("contrib.errors.overwriting_properties"));
        }
      }
    }

    // Remove any remaining boogers
    if (tempFolder.exists()) {
      Util.removeDir(tempFolder, false);
    }
    return installedContrib;
  }


  public boolean isInstalled() {
    return false;
  }


  public ContributionType getType() {
    return type;
  }


  /**
   * We overwrite those fields that aren't proper in the properties file with
   * the curated version from the Processing site. This ensures that things have
   * been cleaned up (for instance, that the "sentence" is really a sentence)
   * and that bad data from the contrib's .properties file doesn't break the
   * manager. However, it also ensures that valid fields in the properties file
   * aren't overwritten, since the properties file may be more recent than the
   * contributions.txt file.
   */
  public boolean writePropertiesFile(File propFile) {
    try {
      StringDict properties = Util.readSettings(propFile);

      String name = properties.get("name");
      if (name == null || name.isEmpty()) {
        name = getName();
      }

      String category;
      StringList categoryList = parseCategories(properties);
      if (categoryList.size() == 1 &&
          categoryList.get(0).equals(UNKNOWN_CATEGORY)) {
        category = getCategoryStr();
      } else {
        category = categoryList.join(",");
      }

      StringList importsList = parseImports(properties);

      String authors = properties.get(AUTHORS_PROPERTY);
//      if (authors == null) {
//        authors = properties.get("authorList");  // before 3.0a11
//      }
      if (authors == null || authors.isEmpty()) {
        authors = getAuthorList();
      }

      String url = properties.get("url");
      if (url == null || url.isEmpty()) {
        url = getUrl();
      }

      String sentence = properties.get("sentence");
      if (sentence == null || sentence.isEmpty()) {
        sentence = getSentence();
      }

      String paragraph = properties.get("paragraph");
      if (paragraph == null || paragraph.isEmpty()) {
        paragraph = getParagraph();
      }

      int version;
      try {
        version = Integer.parseInt(properties.get("version"));
      } catch (NumberFormatException e) {
        version = getVersion();
        System.err.println("The version number for “" + name + "” is not a number.");
        System.err.println("Please contact the author to fix it according to the guidelines.");
      }

      String prettyVersion = properties.get("prettyVersion");
      if (prettyVersion != null && prettyVersion.isEmpty()) {
        prettyVersion = null;
      }

      String compatibleContribsList = null;
      if (getType() == ContributionType.EXAMPLES) {
        compatibleContribsList = properties.get(MODES_PROPERTY);
      }

      long lastUpdated;
      try {
        lastUpdated = Long.parseLong(properties.get("lastUpdated"));
      } catch (NumberFormatException nfe) {
        lastUpdated = getLastUpdated();
      // Better comment these out till all contribs have a lastUpdated
//        System.err.println("The last updated date for the “" + name
//                           + "” contribution is not set properly.");
//        System.err
//          .println("Please contact the author to fix it according to the guidelines.");
      }

      int minRev;
      try {
        minRev = Integer.parseInt(properties.get("minRevision"));
      } catch (NumberFormatException e) {
        minRev = getMinRevision();
//        System.err.println("The minimum compatible revision for the “" + name
//          + "” contribution is not set properly. Assuming minimum revision 0.");
      }

      int maxRev;
      try {
        maxRev = Integer.parseInt(properties.get("maxRevision"));
      } catch (NumberFormatException e) {
        maxRev = getMaxRevision();
//        System.err.println("The maximum compatible revision for the “" + name
//                           + "” contribution is not set properly. Assuming maximum revision INF.");
      }

      if (propFile.delete() && propFile.createNewFile() && propFile.setWritable(true)) {
        PrintWriter writer = PApplet.createWriter(propFile);

        writer.println("name=" + name);
        writer.println("category=" + category);
        writer.println(AUTHORS_PROPERTY + "=" + authors);
        writer.println("url=" + url);
        writer.println("sentence=" + sentence);
        writer.println("paragraph=" + paragraph);
        writer.println("version=" + version);
        if (prettyVersion != null) {
          writer.println("prettyVersion=" + prettyVersion);
        }
        writer.println("lastUpdated=" + lastUpdated);
        writer.println("minRevision=" + minRev);
        writer.println("maxRevision=" + maxRev);
        if ((getType() == ContributionType.LIBRARY || getType() == ContributionType.MODE) && importsList != null) {
          writer.println("imports=" + importsList.join(","));
        }
        if (getType() == ContributionType.EXAMPLES) {
          if (compatibleContribsList != null) {
            writer.println(MODES_PROPERTY + "=" + compatibleContribsList);
          }
        }

        writer.flush();
        writer.close();
      }
      return true;

    } catch (FileNotFoundException e) {
      e.printStackTrace();

    } catch (IOException e) {
      e.printStackTrace();
    }
    return false;
  }
}
