package org.basex.query.util.pkg;

import static org.basex.query.util.Err.*;
import static org.basex.query.util.pkg.PkgText.*;
import static org.basex.util.Token.*;

import java.io.FileNotFoundException;
import java.io.IOException;
import org.basex.io.IO;
import org.basex.io.IOContent;
import org.basex.io.IOFile;
import org.basex.io.Zip;
import org.basex.query.QueryException;
import org.basex.query.util.pkg.Package.Dependency;
import org.basex.util.InputInfo;
import org.basex.util.Util;

/**
 * Repository manager.
 *
 * @author BaseX Team 2005-11, BSD License
 * @author Rositsa Shadura
 */
public final class RepoManager {
  /** Repository context. */
  private final Repo repo;

  /**
   * Constructor.
   * @param r repository context
   */
  public RepoManager(final Repo r) {
    repo = r;
  }

  /**
   * Installs a new package.
   * @param path package path
   * @param ii input info
   * @throws QueryException query exception
   */
  public void install(final String path, final InputInfo ii)
      throws QueryException {

    // check if package exists, and cache contents
    final IO io = IO.get(path);
    IOContent cont = null;
    try {
      cont = new IOContent(io.read());
    } catch(final IOException ex) {
      Util.debug(ex);
      PKGNOTEXIST.thrw(ii, path);
    }

    try {
      // parse and validate repository
      final Zip zip = new Zip(cont);
      final byte[] dsc = zip.read(DESCRIPTOR);
      final Package pkg = new PkgParser(repo, ii).parse(new IOContent(dsc));
      new PkgValidator(repo, ii).check(pkg);

      // choose unique directory, unzip files and register repository
      final IOFile file = uniqueDir(
          string(pkg.uniqueName()).replaceAll("[^\\w.-]+", "-"));
      zip.unzip(file);
      repo.add(pkg, file.name());
    } catch(final FileNotFoundException ex) {
      Util.debug(ex);
      PKGREADFNF.thrw(ii, io.name(), ex.getMessage());
    } catch(final IOException ex) {
      Util.debug(ex);
      PKGREADFAIL.thrw(ii, io.name(), ex.getMessage());
    }
  }

  /**
   * Returns a unique directory for the specified package.
   * @param n name
   * @return unique directory
   */
  private IOFile uniqueDir(final String n) {
    String nm = n;
    int c = 0;
    do {
      final IOFile io = repo.path(nm);
      if(!io.exists()) return io;
      nm = n + '-' + ++c;
    } while(true);
  }

  /**
   * Removes a package from package repository.
   * @param pkg package
   * @param ii input info
   * @throws QueryException query exception
   */
  public void delete(final String pkg, final InputInfo ii)
      throws QueryException {
    boolean found = false;
    for(final byte[] nextPkg : repo.pkgDict()) {
      if(nextPkg == null) continue;

      final byte[] dir = repo.pkgDict().get(nextPkg);
      if(eq(Package.name(nextPkg), token(pkg)) || eq(dir, token(pkg))) {
        // a package can be deleted either by its name or by its directory name
        found = true;
        // check if package to be deleted participates in a dependency
        final byte[] primPkg = primary(nextPkg, ii);
        if(primPkg != null) PKGDEP.thrw(ii, string(primPkg), pkg);

        // clean package repository
        final IOFile f = repo.path(string(dir));
        final IOFile desc = new IOFile(f, DESCRIPTOR);
        repo.remove(new PkgParser(repo, ii).parse(desc));
        // package does not participate in a dependency => delete it
        if(!f.delete()) CANNOTDELPKG.thrw(ii);
      }
    }
    if(!found) PKGNOTEXIST.thrw(ii, pkg);
  }

  /**
   * Checks if a package participates in a dependency.
   * @param pkgName package
   * @param ii input info
   * @return package depending on the current one
   * @throws QueryException query exception
   */
  private byte[] primary(final byte[] pkgName, final InputInfo ii)
      throws QueryException {
    for(final byte[] nextPkg : repo.pkgDict()) {
      if(nextPkg != null && !eq(nextPkg, pkgName)) {
        // check only packages different from the current one
        final IOFile desc = new IOFile(repo.path(
            string(repo.pkgDict().get(nextPkg))), DESCRIPTOR);
        final Package pkg = new PkgParser(repo, ii).parse(desc);
        final byte[] name = Package.name(pkgName);
        for(final Dependency dep : pkg.dep)
          // Check only package dependencies
          if(dep.pkg != null && eq(dep.pkg, name)) return Package.name(nextPkg);
      }
    }
    return null;
  }
}
