package search.inno;

import java.io.File;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.log4j.Logger;

import bean.Content;
import bean.InnoResult;
import bean.Track;
import br.com.innovox.apps.managers.ExtractorManager;
import br.com.innovox.apps.managers.FinderManager;
import br.com.innovox.apps.managers.LutManager;
import br.com.innovox.lib.soundmatch.IdtResult;
import utils.AudioUtils;

public class InnoSearcher {

	private static final Logger logger = Logger.getLogger(InnoSearcher.class);

	private LutManager lut;
	private ExtractorManager extractor;
	private FinderManager finder;
	private AudioUtils audioUtils;

	public InnoSearcher() {
		
		this.initLut();

		this.audioUtils = new AudioUtils();
	}
	
	private final void initLut() {
		
		logger.info("initLut: Initializing Lut.");
		
		this.extractor = new ExtractorManager();
		this.lut = new LutManager(LutManager.DB_MEDIUM_SIZE);
		this.finder = new FinderManager(lut);		
	}
	
	public boolean indexAll(List<Content> contents) {

		int indexCount = 0;

		for (Content content : contents) {

			try {
				byte[] wav = this.audioUtils.toWave(content.getContentFile());

				if (wav == null) {
					logger.error("index: Could not generate WAV data from "+content.getContentFile().getAbsolutePath());
					continue;
				}

				byte[] fingerprint = ExtractorManager.Process(wav);

				this.lut.AddFingerprint(fingerprint, content.getContentId());

				logger.info("index: Indexed "+content.getContentFile().getName()+" (" + ++indexCount + "/" + contents.size() + ")");
			}
			catch (Exception e) {
				logger.error("indexAll: Could not index: "+content, e);
				return false;
			}
		}
		
		logger.info("indexAll: Indexed "+indexCount+" contents.");
		
		return true;
	}

	public List<InnoResult> search(Track track) {
		
		List<InnoResult> result = new ArrayList<InnoResult>();
		
		IdtResult[] searchResult = null;
		
		try {
			searchResult = this.search(track.getTrack(), this.toDate(track.getCreationTime()));
		} catch (Exception e) {
			logger.error("search: Could not perform search for track: "+track);
		}
		
		if (searchResult != null) {
			
			for (IdtResult idtResult : searchResult) {
				
				for (int i = 0 ; i < idtResult.ids.length ; i++) {
					
					if (idtResult.ids[i] != 0) {
						
						result.add(new InnoResult(idtResult.ids[i], idtResult.distances[i]));
					}
				}
			}
		}
		
		return result;
	}

	private IdtResult[] search(File query, Date queryCreationTime) throws Exception  {
		
		logger.info("search: Searching "+query.getAbsolutePath());

		// Reading wav file
		byte[] wavstream = this.audioUtils.toWave(query);
		if (wavstream == null) {
			logger.error("search: Could not generate WAV data from "+query.getAbsolutePath());
			return null;
		}		

		// Processing -- Feature Extration
		wavstream = extractor.AlingWave(wavstream, queryCreationTime);
		
		if (wavstream == null) {
			logger.error("search: Could not align WAV data from "+query.getAbsolutePath());
			return null;
		}

		byte[] fingerprint = extractor.Process(wavstream, ExtractorManager.FLAG_NORMAL_FRAME);
		
		return finder.SearchMany(fingerprint, FinderManager.FLAG_NORMAL_FRAME);		
	}
	
	private final Date toDate(LocalDateTime date) {
		
		return Date.from(date.atZone(ZoneId.systemDefault()).toInstant());
	}

	public void clear() {
		
		logger.info("clear: Cleaning InnoSearcher index.");
		
		this.initLut();
	}
}
