/** {@inheritDoc} */
 @Override
 public boolean exists() {
   if (this.connection != null) return true;
   try {
     this.connection = WebDavTools.connect(this.uri);
     return this.connection != null;
   } catch (IOException e) {
     LOGGER.warn("Could not connect to '" + this.uri + "'.", e);
     this.connection = null;
     return false;
   }
 }
  /** {@inheritDoc} */
  @Override
  public void run() {
    while (!this.isShutdown) {

      // handle "everything is empty"
      this.addLock.lock();
      try {
        // sleep if there is nothing to do
        while (this.pool.size() + this.queue.size() + this.add.size() + this.addQueue.size() == 0) {
          if (this.threadIsEmpty.awaitNanos(TimeUnit.MINUTES.toNanos(2)) <= 0 || this.isShutdown) {
            this.isShutdown = true;
            this.pool.clear();
            return;
          }
        }
        this.pool.addAll(this.add);
        this.add.clear();
        this.queue.addAll(this.addQueue);
        this.addQueue.clear();
      } catch (InterruptedException e) {
        this.isShutdown = true;
        this.pool.clear();
        this.queue.clear();
        return;
      } finally {
        this.addLock.unlock();
      }

      // remove jobs on request
      this.removeLock.lock();
      try {
        this.pool.removeAll(this.remove);
        Iterator<Entry> iter = this.queue.iterator();
        while (iter.hasNext()) if (this.remove.contains(iter.next().job)) iter.remove();
        this.remove.clear();
      } finally {
        this.removeLock.unlock();
      }

      // first handle normal jobs
      Iterator<IDoJob> poolIT = this.pool.iterator();
      while (poolIT.hasNext()) {
        IDoJob job = poolIT.next();
        if (!job.isfinished())
          try {
            job.doJob();
          } catch (Exception e) {
            LOGGER.severe(
                "The job " + job.name() + " threw an exception and has been subsequently removed.",
                e);
            poolIT.remove();
            this._shutdownJob(job);
          }
        else poolIT.remove();
      }

      if (!this.queue.isEmpty()) {
        final long ctime = Platform.currentTime();
        // first work on jobs that are overdue
        Iterator<Entry> queueIT = this.queue.iterator();
        while (queueIT.hasNext()) {
          Entry entry = queueIT.next();
          if (entry.time > ctime) break;
          queueIT.remove();
          if (!entry.job.isfinished()) {
            try {

              if (entry.loop != Entry.NO_LOOP) {
                entry.job.doJob();
                entry.time = Math.max(entry.time + entry.loop, ctime);
                entry._decipher = Entry.DECIPHER++;
                this.addLock.lock();
                try {
                  this.addQueue.add(entry);
                } finally {
                  this.addLock.unlock();
                }
              } else this.addJob(entry.job);
            } catch (Exception e) {
              LOGGER.severe(
                  "The job "
                      + entry.job.name()
                      + " threw an exception and has been subsequently removed.",
                  e);
              this._shutdownJob(entry.job);
            }
          }
        }
      }
      this.addLock.lock();
      try {
        long time = 0;
        // we need to sleep when there are jobs in the queue that should not be worked on yet, but
        // there are no jobs in the pool
        while (this.pool.size() + this.add.size() + this.addQueue.size() == 0
            && !this.queue.isEmpty()
            && (time = this.queue.peek().time - Platform.currentTime()) > 5) {
          this.threadIsEmpty.await(time, TimeUnit.MILLISECONDS);
        }
      } catch (InterruptedException e) {
        // do nothing just check the conditions above
      } finally {
        this.addLock.unlock();
      }
    }
    for (IDoJob job : this.pool) this._shutdownJob(job);
    for (Entry entry : this.queue) this._shutdownJob(entry.job);
    this.pool.clear();
    this.queue.clear();
  }
/**
 * TODO javadoc
 *
 * @author funsheep
 */
final class DoJobThread extends Thread {

  private static final Logger LOGGER = Logger.getLogger();

  private static class Entry implements Comparable<Entry> {
    private static volatile long DECIPHER = Long.MIN_VALUE;

    public static final long NO_LOOP = -1;

    public long time;
    private long _decipher;
    public IDoJob job;
    public long loop;

    /**
     * @param time The execution time of this entry (event).
     * @param event The event which has to be scheduled.
     * @param listener The listener to call.
     * @param loop The information if the given entry encapsulates a loop event.
     */
    public Entry(IDoJob job, long time) {
      this(job, time, NO_LOOP);
    }

    /**
     * @param time The execution time of this entry (event).
     * @param event The event which has to be scheduled.
     * @param listener The listener to call.
     * @param loop The information if the given entry encapsulates a loop event.
     */
    public Entry(IDoJob job, long time, long loop) {
      this.job = job;
      this.time = time;
      this.loop = loop;
      this._decipher = DECIPHER++;
    }

    /** {@inheritDoc} */
    @Override
    public int compareTo(Entry o) {
      final long t = this.time - o.time;
      if (t < 0) return -1;
      if (t > 0) return +1;

      final long d = this._decipher - o._decipher;
      if (d < 0) return -1;
      if (d > 0) return +1;
      return 0;
    }
  }

  private final ReentrantLock addLock = new ReentrantLock();
  private final Condition threadIsEmpty = this.addLock.newCondition();
  private final ReentrantLock removeLock = new ReentrantLock();

  private final SmallSet<IDoJob> add = new SmallSet<>(1);
  private final SmallSet<IDoJob> remove = new SmallSet<>(1);
  private final SmallSet<IDoJob> pool = new SmallSet<>(2);
  private final SmallSet<Entry> addQueue = new SmallSet<>(1);
  private final PriorityQueue<Entry> queue = new PriorityQueue<>(2);

  private volatile boolean isShutdown = false;

  /** @param name */
  public DoJobThread(String name) {
    super(name);
    this.setDaemon(true);
    this.start();
  }

  /** {@inheritDoc} */
  @Override
  public void run() {
    while (!this.isShutdown) {

      // handle "everything is empty"
      this.addLock.lock();
      try {
        // sleep if there is nothing to do
        while (this.pool.size() + this.queue.size() + this.add.size() + this.addQueue.size() == 0) {
          if (this.threadIsEmpty.awaitNanos(TimeUnit.MINUTES.toNanos(2)) <= 0 || this.isShutdown) {
            this.isShutdown = true;
            this.pool.clear();
            return;
          }
        }
        this.pool.addAll(this.add);
        this.add.clear();
        this.queue.addAll(this.addQueue);
        this.addQueue.clear();
      } catch (InterruptedException e) {
        this.isShutdown = true;
        this.pool.clear();
        this.queue.clear();
        return;
      } finally {
        this.addLock.unlock();
      }

      // remove jobs on request
      this.removeLock.lock();
      try {
        this.pool.removeAll(this.remove);
        Iterator<Entry> iter = this.queue.iterator();
        while (iter.hasNext()) if (this.remove.contains(iter.next().job)) iter.remove();
        this.remove.clear();
      } finally {
        this.removeLock.unlock();
      }

      // first handle normal jobs
      Iterator<IDoJob> poolIT = this.pool.iterator();
      while (poolIT.hasNext()) {
        IDoJob job = poolIT.next();
        if (!job.isfinished())
          try {
            job.doJob();
          } catch (Exception e) {
            LOGGER.severe(
                "The job " + job.name() + " threw an exception and has been subsequently removed.",
                e);
            poolIT.remove();
            this._shutdownJob(job);
          }
        else poolIT.remove();
      }

      if (!this.queue.isEmpty()) {
        final long ctime = Platform.currentTime();
        // first work on jobs that are overdue
        Iterator<Entry> queueIT = this.queue.iterator();
        while (queueIT.hasNext()) {
          Entry entry = queueIT.next();
          if (entry.time > ctime) break;
          queueIT.remove();
          if (!entry.job.isfinished()) {
            try {

              if (entry.loop != Entry.NO_LOOP) {
                entry.job.doJob();
                entry.time = Math.max(entry.time + entry.loop, ctime);
                entry._decipher = Entry.DECIPHER++;
                this.addLock.lock();
                try {
                  this.addQueue.add(entry);
                } finally {
                  this.addLock.unlock();
                }
              } else this.addJob(entry.job);
            } catch (Exception e) {
              LOGGER.severe(
                  "The job "
                      + entry.job.name()
                      + " threw an exception and has been subsequently removed.",
                  e);
              this._shutdownJob(entry.job);
            }
          }
        }
      }
      this.addLock.lock();
      try {
        long time = 0;
        // we need to sleep when there are jobs in the queue that should not be worked on yet, but
        // there are no jobs in the pool
        while (this.pool.size() + this.add.size() + this.addQueue.size() == 0
            && !this.queue.isEmpty()
            && (time = this.queue.peek().time - Platform.currentTime()) > 5) {
          this.threadIsEmpty.await(time, TimeUnit.MILLISECONDS);
        }
      } catch (InterruptedException e) {
        // do nothing just check the conditions above
      } finally {
        this.addLock.unlock();
      }
    }
    for (IDoJob job : this.pool) this._shutdownJob(job);
    for (Entry entry : this.queue) this._shutdownJob(entry.job);
    this.pool.clear();
    this.queue.clear();
  }

  private void _shutdownJob(final IDoJob job) {
    Thread th =
        new Thread() {

          @Override
          public void run() {
            try {
              job.shutdown();
            } catch (Exception e) {
              LOGGER.info("Shutting down job '{}' caused an exception.", job.name(), e);
            }
          }
        };
    th.start();
    this.removeJob(job);
  }

  public boolean addJob(IDoJob job) {
    this.addLock.lock();
    try {
      if (this.isShutdown) return false;

      this.add.add(job);
      this.threadIsEmpty.signal();
      return true;
    } finally {
      this.addLock.unlock();
    }
  }

  public boolean scheduleJob(IDoJob job, long delay) {
    this.addLock.lock();
    try {
      if (this.isShutdown) return false;

      this.addQueue.add(new Entry(job, Platform.currentTime() + delay));
      this.threadIsEmpty.signal();
      return true;
    } finally {
      this.addLock.unlock();
    }
  }

  public boolean loopJob(IDoJob job, long interExecTime) {
    this.addLock.lock();
    try {
      if (this.isShutdown) return false;

      this.addQueue.add(new Entry(job, Platform.currentTime(), interExecTime));
      this.threadIsEmpty.signal();
      return true;
    } finally {
      this.addLock.unlock();
    }
  }

  public void removeJob(IDoJob job) {
    this.removeLock.lock();
    try {
      this.remove.add(job);
    } finally {
      this.removeLock.unlock();
    }
  }

  public void shutdown() {
    this.addLock.lock();
    try {
      this.isShutdown = true;
      this.threadIsEmpty.signal();
      this.interrupt();
    } finally {
      this.addLock.unlock();
    }
  }

  public boolean isShutdown() {
    this.addLock.lock();
    try {
      return this.isShutdown;
    } finally {
      this.addLock.unlock();
    }
  }
}
/**
 * TODO javadoc
 *
 * @author MeisterYeti
 */
public class InternalWebDavDirectory implements IInternalDirectory {
  private static final Logger LOGGER = Logger.getLogger();
  private static final String HREF_PATTERN = "<a .*?href=\"(.*?)\"";

  private final URI uri;
  private URLConnection connection;

  /** */
  public InternalWebDavDirectory(URI uri) {
    this.uri = uri;
  }

  /** {@inheritDoc} */
  @Override
  public URI uri() {
    return this.uri;
  }

  /** {@inheritDoc} */
  @Override
  public int resourceType() {
    return IResourceAPI.TYPE_FILE;
  }

  /** {@inheritDoc} */
  @Override
  public boolean exists() {
    if (this.connection != null) return true;
    try {
      this.connection = WebDavTools.connect(this.uri);
      return this.connection != null;
    } catch (IOException e) {
      LOGGER.warn("Could not connect to '" + this.uri + "'.", e);
      this.connection = null;
      return false;
    }
  }

  /** {@inheritDoc} */
  @Override
  public String name() {
    return URIs.extractName(this.uri);
  }

  /** {@inheritDoc} */
  @Override
  public boolean isWritable() {
    return false;
  }

  /** {@inheritDoc} */
  @Override
  public URI[] getChildren() throws IOException {
    if (!this.exists()) return null;

    if (!this.connection.getContentType().startsWith("text/html")) return null;

    BufferedReader in = new BufferedReader(new InputStreamReader(this.connection.getInputStream()));
    String line = null;
    Pattern pattern = Pattern.compile(HREF_PATTERN);

    SmallSet<URI> uris = new SmallSet<URI>();

    while ((line = in.readLine()) != null) {
      Matcher matcher = pattern.matcher(line);
      while (matcher.find()) {
        String match = matcher.group(1);
        // TODO: Find better method to filter out non-relative urls
        if (!match.startsWith("/") && !match.contains("://") && !match.contains("?")) {
          uris.add(this.uri.resolve(match));
        }
      }
    }
    return uris.toArray(new URI[uris.size()]);
  }

  /** {@inheritDoc} */
  @Override
  public void discard() {
    // do nothing
  }

  /** {@inheritDoc} */
  @Override
  public boolean create() throws IOException {
    throw new UnsupportedOperationException("Creating directories is not permitted.");
  }

  /** {@inheritDoc} */
  @Override
  public boolean ensureExistence() throws IOException {
    if (this.exists()) return false;
    return this.create();
  }

  /** {@inheritDoc} */
  @Override
  public boolean delete() throws IOException {
    throw new UnsupportedOperationException("Deleting directories is not permitted.");
  }

  /** {@inheritDoc} */
  @Override
  public void close() {
    this.connection = null;
  }
}