/*
 *  Copyright 2006-2007 Columbia University.
 *
 *  This file is part of MEAPsoft.
 *
 *  MEAPsoft is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  MEAPsoft 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 MEAPsoft; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 *  02110-1301 USA
 *
 *  See the file "COPYING" for the text of the license.
 */

package com.meapsoft.composers;

import gnu.getopt.Getopt;

import java.io.IOException;
import java.text.NumberFormat;
import java.util.Iterator;

import com.meapsoft.ChunkDist;
import com.meapsoft.EDLChunk;
import com.meapsoft.EDLFile;
import com.meapsoft.EuclideanDist;
import com.meapsoft.FeatChunk;
import com.meapsoft.FeatFile;
import com.meapsoft.ParserException;

/**
 * Program that composes a mashup by replacing each segment of the
 * input file with the chunk in a given chunk database whos features
 * most closely match those of the input chunk.  
 *
 * Produces an EDL from two feature files.  Basically an offline
 * version of Mike Mandel's mashup generator.
 *
 * @author Ron Weiss (ronw@ee.columbia.edu)
 */
public class MashupComposer extends Composer
{
    public static String description = "MashupComposer attempts to match chunks in the input features file using " +
	"chunks from the chunk database features file. The result is the source sound " +
	"file created from chunks in the chunk database.";
	
    String outFileName = "mashup.edl";
    
    FeatFile dstFile;
    FeatFile DBFile;
    int[] featdim = null;
    ChunkDist dist;

    public MashupComposer(String featFN, String DBFN, String outFN)
    {
        this(featFN, DBFN, outFN, new EuclideanDist());
    }

    public MashupComposer(String featFN, String DBFN, String outFN, ChunkDist cd)
    {
        this(new FeatFile(featFN), new FeatFile(DBFN), new EDLFile(outFN), cd);
    }

    public MashupComposer(FeatFile featFN, FeatFile DBFN, EDLFile outFN)
    {
        this(featFN, DBFN, outFN, new EuclideanDist());
    }

    public MashupComposer(FeatFile featFN, FeatFile DBFN, EDLFile outFN, ChunkDist cd)
    {
        dstFile = featFN;
        DBFile = DBFN;
        outFile = outFN;
        dist = cd;
        featdim = cd.featdim;

        if(outFile == null)
            outFile = new EDLFile("");
    }

    public void printUsageAndExit() 
    {
        System.out.println("Usage: MashupComposer [-options] dest.feat chunkdb.feat \n\n" + 
		       "  where options include:\n" + 
		       "    -o output_file  the file to write the output to (defaults to mashup.edl)");
        printCommandLineOptions('i');
        printCommandLineOptions('d');
        printCommandLineOptions('c');
        System.out.println();
        System.exit(0);
    }

    /**
     * MashupComposer constructor.  Parses command line arguments
     */
    public MashupComposer(String[] args) 
    {
        if(args.length == 0)
            printUsageAndExit();

        // Parse arguments
        String argString = "d:i:o:c:";
        featdim = parseFeatDim(args, argString);
        dist = parseChunkDist(args, argString, featdim);
        parseCommands(args, argString);
       
        Getopt opt = new Getopt("MashupComposer", args, argString);
        opt.setOpterr(false);

        int c = -1;
        while ((c = opt.getopt()) != -1) 
        {
            switch(c) 
            {
            case 'o':
                outFileName = opt.getOptarg();
                break;
            case 'd':  // already handled above
                break;
            case 'i':  // already handled above
                break;
            case 'c':  // already handled above
                break;
            case '?':
                printUsageAndExit();
                break;
            default:
                System.out.print("getopt() returned " + c + "\n");
            }
        }

        // parse arguments
        int ind = opt.getOptind();
        if(ind > args.length)
            printUsageAndExit();
        
        dstFile = new FeatFile(args[args.length-2]);
        DBFile = new FeatFile(args[args.length-1]);
        outFile = new EDLFile(outFileName);

        System.out.println("Composing " + outFileName + 
                           " from " +  args[args.length-2] +
                           " (chunk db: " + args[args.length-1] + ").");
    }

    public void setup() throws IOException, ParserException
    {
        super.setup();

        if(!dstFile.haveReadFile)
            dstFile.readFile();
        if(!DBFile.haveReadFile)
            DBFile.readFile();
        
        if(dstFile.chunks.size() == 0)  
            throw new ParserException(dstFile.filename, "No chunks found");
        if(DBFile.chunks.size() == 0)
            throw new ParserException(DBFile.filename, "No chunks found");

        // What if features don't match
        if(!dstFile.isCompatibleWith(DBFile))
            throw new ParserException(DBFile.filename, 
                          "Features do not match those in " + dstFile.filename);

        dstFile = (FeatFile)dstFile.clone();
        dstFile.normalizeFeatures();
        dstFile.applyFeatureWeights();

        DBFile = (FeatFile)DBFile.clone();
        DBFile.normalizeFeatures();
        DBFile.applyFeatureWeights();

        progress.setMaximum(dstFile.chunks.size());
    }

    public EDLFile compose()
    {
        NumberFormat fmt = NumberFormat.getInstance();
        fmt.setMaximumFractionDigits(3);

        Iterator dstchunks = dstFile.chunks.iterator();
        while(dstchunks.hasNext())
        {
            FeatChunk currChunk = (FeatChunk)dstchunks.next();
            double mindist = Double.MAX_VALUE;
            FeatChunk match = null;
            
            // find closest match to currChunk in DB
            Iterator i = DBFile.chunks.iterator();
            while(i.hasNext())
            {
                FeatChunk c = (FeatChunk)i.next();
                double d = dist.distance(currChunk, c);

                if(d < mindist)
                {
                    mindist = d;
                    match = c;
                }
            }
            
            // turn match chunk into an EDL chunk 
            EDLChunk nc = new EDLChunk(match, currChunk.startTime);
            nc.comment = "    # dist = " + fmt.format(mindist);
            outFile.chunks.add(nc);

            progress.setValue(progress.getValue()+1);
        }

        // outFile now contains some chunks.
        outFile.haveReadFile = true;

        return outFile;
    } 

    public static void main(String[] args) 
    {
        MashupComposer m = new MashupComposer(args);
        long startTime = System.currentTimeMillis();
        m.run();
        System.out.println("Done. Took " +
                           ((System.currentTimeMillis() - startTime)/1000.0)
                           + "s");
        System.exit(0);
    }
}
