/*
 *    AudioDBWrapper.java
 *    Copyright (C) 2011 New Zealand Digital Library, http://www.nzdl.org
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package org.greenstone.gsdl3.util;

import java.io.*;
import java.util.Vector;
import java.util.Collections;

import org.apache.log4j.*;

/** java wrapper class for access to the audioDB executable
 *
 * Inspired by MGSearchWrapper.java
 */

public class AudioDBWrapper
{
    /** the query result, filled in by runQuery */
    protected Vector query_result_;

    protected int offset_ = 100;
    protected int length_ = 20;
    
    // Approximate matching not yet utilized
    protected double radius_;

    protected int max_docs_;

    static Logger logger = Logger.getLogger (org.greenstone.gsdl3.util.AudioDBWrapper.class.getName ());

    public AudioDBWrapper() {  
	query_result_ = null;
    }

    // query param methods

    /** start point (offset) into the array of feature vectors for a track 
	- 100 by default which equals 10 seconds (assuming 0.1 frame size) */
    public void setOffset(int offset) {
	offset_ = offset;
    }
	
    /** the number of consecutive frames used in match
	- 20 by default which equals 2 seconds (assuming 0.1 frame size) */
    public void setLength(int length) {
	length_ = length;
    }

    /** distance used in approximate matching support - default is 50 */
    public void setRadius(double radius) {
	radius_ = radius;
    }

    public void setMaxDocs(int max_docs) {
	max_docs_ = max_docs;
    }

    /** returns a string with all the current query param settings */
    // the following was in MG version, do we need this in audioDB version?
    //public String getQueryParams() {}


    protected boolean addQueryResult(boolean first_entry, String doc_id, 
				  Vector<Double> rankVector, Vector<Integer> offsetVector)
    {

	if (first_entry) {
	    AudioDBDocInfo audioDB_doc_info = new AudioDBDocInfo(doc_id,rankVector,offsetVector);
	    query_result_.add(audioDB_doc_info);
	    first_entry = false;
	}
	else {
	    double rank = rankVector.get(0);
	    int offset = offsetVector.get(0);
	    AudioDBDocInfo audioDB_doc_info = new AudioDBDocInfo(doc_id,rank,offset);
	    
	    query_result_.add(audioDB_doc_info);
	}

	return first_entry;
    }


    /** actually carry out the query.
	Use the set methods to set query results.
	Writes the result to query_result.
     * - maintains state between requests as can be slow  
     * base_dir and index_path should join together to provide
     * the absolute location of the mg index files eg ..../index/dtx/demo 
     * base_dir must end with a file separator (OS dependant)
     */
    public void runQuery(String audioDB_index_dir, String adb_file, 
			 String assoc_index_dir, String query_string) {

	// combine index_dir with audiodb fileanem

	String full_adb_filename  = audioDB_index_dir + File.separatorChar + adb_file;
	String full_chr12_filename = assoc_index_dir + File.separatorChar 
	    + query_string + File.separatorChar + "doc.chr12";

	int num_matches_within_track = 6;

	String [] cmd_array = new String[] {
	    "audioDB",
	    "-d", full_adb_filename,
	    "-Q", "nsequence",
	    "-p", String.format("%d",offset_),
	    "-n", String.format("%d",num_matches_within_track),
	    "-l", String.format("%d",length_),
	    "-r", String.format("%d",max_docs_),
	    "-f", full_chr12_filename
	};

	System.err.println("**** cmd_array = " + String.join(" ", cmd_array));

	Runtime runtime = Runtime.getRuntime();
	try {
	    Process audioDB_proc = runtime.exec(cmd_array);
	    //int exitVal = audioDB_proc.waitFor();
	    //System.err.println("*** exit status = " + exitVal);

	    InputStream ais = audioDB_proc.getInputStream();
	    InputStreamReader aisr = new InputStreamReader(ais);
	    BufferedReader abr = new BufferedReader(aisr);

	    query_result_ = new Vector();

	    boolean first_entry = true;
	    int line_count = 0;

	    String root_doc_id = null;
	    Vector<Double> rankVector = new Vector<Double>();
	    Vector<Integer> offsetVector = new Vector<Integer>();

	    // Example output
	    //   D8 0.00105175
	    //   1.69786e-16 392 392
	    //   0.00113568 392 673
	    //   0.00127239 392 910
	    //   0.00139736 392 481
	    //   0.00145331 392 303
	    //   D2 0.00429758
	    //   0.00403335 392 865
	    //   0.00411288 392 458
	    //   0.00442461 392 866
	    //   0.00444272 392 864
	    //   0.00447434 392 424
	    // ...

	    String line;
	    while ((line = abr.readLine()) != null) {
		String[] tokens = line.split("\\s+");
		line_count++;

		if (tokens.length==2) {
		    // processing a top-level doc line

		    if (line_count>1) {
			// struck new top-level entry => store vector vals for previous block

			first_entry = addQueryResult(first_entry,root_doc_id,rankVector,offsetVector);
			// and now reset vectors to empty to be ready for next chain of values
			rankVector = new Vector<Double>();
			offsetVector = new Vector<Integer>();
		    }

		    root_doc_id = tokens[0];
		}
		else {
		    // should be 3 items 
		    double euclidean_dist = Double.parseDouble(tokens[0]);
		    int src_frame = Integer.parseInt(tokens[1]);
		    int target_frame = Integer.parseInt(tokens[2]);

		    // enforce 1.0 as upper limit due to rounding errors 
		    // in audioDB distance calculations
		    double rank = Math.min(1.0 - euclidean_dist,1.0); 

		    if ((line_count==2) && (src_frame==target_frame)) {
			// Found match with self
			continue;
		    }

		    rankVector.add(rank);
		    offsetVector.add(target_frame);
		}

	    }

	    addQueryResult(first_entry,root_doc_id,rankVector,offsetVector);

	    abr.close();

	    // sort query_result_ on 'rank' field 
	    // note: compareTo() method impelemented to sort into descending order

	    Collections.sort(query_result_);


	}
	catch (Exception e) {
	    logger.error("Failed to execute the following command: " +  String.join(" ", cmd_array));
	    e.printStackTrace();
	}

    }


    /** get the result out of the wrapper */
    public Vector getQueryResult()
    {
	return query_result_;
    }
}

