/*
 * Copyright 2012  Research Studios Austria Forschungsges.m.b.H.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package won.bot.framework.component.needproducer.impl;

import org.apache.jena.rdf.model.Model;
import org.apache.commons.io.filefilter.RegexFileFilter;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import won.bot.framework.component.needproducer.FileBasedNeedProducer;
import won.bot.framework.component.needproducer.NeedProducer;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * NeedProducer that is configured to read needs from a directory.
 */
public class DirectoryBasedNeedProducer implements NeedProducer
{
  private static final int NOT_INITIALIZED = -1;
  private final Logger logger = LoggerFactory.getLogger(getClass());
  private File directory;

  // true if the factory should keep creating needs after having used each file, false if the factory should use each file only once.
  private boolean repeat;

  // Java Regex for filtering filenames in the directory
  private String filenameFilterRegex=null;

  private File[] files;

  private int fileIndex = NOT_INITIALIZED;

  private AtomicBoolean initialized = new AtomicBoolean(false);

  private FileBasedNeedProducer fileBasedNeedProducer;



  @Override
  public synchronized Model create()
  {
    //lazy init
    initializeLazily();
    //init failed?
    if (isInitFailed()) return null;
    //at and of file list?
    if (isAfterLastFile()) {
      if (this.isRepeat()) {
        rewind();
      } else {
        return null;
      }
    }

    //loop until we find a readable file
    while (this.fileIndex < this.files.length){
      if (isCurrentFileReadable()) break;
      this.fileIndex++;
    }
    int fileIndexToUse = this.fileIndex; //remember the current index

    //advance the index for next time, reset it if we've been told to repeat
    this.fileIndex++;
    rewindIfNecessary();

    return readModelFromFileWithIndex(fileIndexToUse);
  }

  public synchronized String getCurrentFileName() {
    return files[fileIndex].getName();
  }

  private boolean isCurrentFileReadable()
  {
    return this.files[this.fileIndex].isFile() && this.files[this.fileIndex].canRead();
  }

  private Model readModelFromFileWithIndex(final int fileIndexToUse)
  {
    try {
      //make a need from it
      return this.fileBasedNeedProducer.readNeedFromFile(this.files[fileIndexToUse]);
    } catch (IOException e) {
      logger.debug("could not read need from file {}", this.files[fileIndexToUse]);
    }
    return null;
  }

  private void rewindIfNecessary()
  {
    if (shouldRewind()) {
      rewind();
    }
  }

  private boolean shouldRewind()
  {
    return this.fileIndex >= this.files.length && this.repeat;
  }

  private void rewind()
  {
    this.fileIndex = 0;
  }

  private boolean isAfterLastFile()
  {
    return fileIndex > files.length;
  }

  private boolean isInitFailed()
  {
    if (this.fileIndex == NOT_INITIALIZED){
      return true;
    }
    return false;
  }

  private synchronized void initializeLazily()
  {
    if (!initialized.get()){
      init();
    }
  }

  @Override
  public boolean isExhausted()
  {
    initializeLazily();
    if (isRepeat() && files != null && files.length > 0) return false;
    return this.fileIndex == NOT_INITIALIZED || this.files == null || this.fileIndex >= this.files.length;
  }

  private synchronized void init(){
    if (this.initialized.get()) return;
    if (this.directory == null){
      logger.warn("No directory specified for DirectoryBasedNeedProducer, not reading any data.");
      return;
    }
    logger.debug("Initializing DirectoryBasedNeedProducer from directory {}", this.directory);
    this.files = directory.listFiles(createFileFilter());
    if (this.files == null || this.files.length == 0) {
      logger.info("no files found in directory {} with regex {}", this.directory, this.filenameFilterRegex);
    } else {
      logger.debug("found {} files in directory {} with regex {}", new Object[]{files.length, this.directory, this.filenameFilterRegex});
    }
    rewind();
    this.initialized.set(true);
  }

  private FileFilter createFileFilter()
  {
    if (this.filenameFilterRegex == null) return TrueFileFilter.TRUE;
    return new RegexFileFilter(this.filenameFilterRegex);
  }


  public File getDirectory()
  {
    return directory;
  }

  public void setDirectory(final File directory)
  {
    this.directory = directory;
  }

  public boolean isRepeat()
  {
    return repeat;
  }

  public void setRepeat(final boolean repeat)
  {
    this.repeat = repeat;
  }

  public FileBasedNeedProducer getFileBasedNeedProducer()
  {
    return fileBasedNeedProducer;
  }

  public void setFileBasedNeedProducer(final FileBasedNeedProducer fileBasedNeedProducer)
  {
    this.fileBasedNeedProducer = fileBasedNeedProducer;
  }

  public String getFilenameFilterRegex()  {    return filenameFilterRegex;  }

  public void setFilenameFilterRegex(final String filenameFilterRegex)      {    this.filenameFilterRegex = filenameFilterRegex;  }
}

@@ -2984,16 +2984,76 @@
 from it%0A
+      if (fileIndexToUse %3E= this.files.length) return null;%0A
       re

return null instead of Exception if exhausted