package kr.midireader.krwriter;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

import javax.sound.midi.MidiSystem;
import javax.sound.midi.Sequence;
import javax.sound.sampled.UnsupportedAudioFileException;

import kr.util.NotificationManager;
import kr.util.audio.AudioClip;
import kr.util.audio.EffectAudioClip;
import kr.util.audio.MemAudioClip;
import kr.util.audio.SubtractedBkgAudioClip;
import kr.util.audio.WritableAudioClip;
import kr.util.sample.KRWavWriter;
import kr.util.sample.SoundRetrievalManager;

/**
 * Packages all the files necessary for a kr song into a single package. 
 */
public class KRPackager 
{
	public static final String DB_DTA_ENTRY = "db.dta.entry";
	public static final String SONGS_DTA_ENTRY = "songs.dta.entry";

	/**
	 * @param vocalsAudio may be null
	 */
	public static void packageKR(String songAbbr, String directory, String dbDta, String songsDta,
			File bkgAudio, File vocalsAudio, Sequence seq, boolean deriveBackground) throws IOException {
		String nTitle = "Create Package";
		NotificationManager.inst().startMultiSegment(nTitle, 2);
		
		String s = File.separator;
		
		ZipOutputStream zo = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(directory+s+songAbbr+".zip")));
		
        zo.putNextEntry(new ZipEntry(DB_DTA_ENTRY));
        zo.write(dbDta.getBytes());
        zo.closeEntry();
        
        zo.putNextEntry(new ZipEntry(SONGS_DTA_ENTRY));
        zo.write(songsDta.getBytes());
        zo.closeEntry();

		NotificationManager.inst().startMultiSegment("Vocals", 3);
        if(vocalsAudio != null)
        {
        	zo.putNextEntry(new ZipEntry("xadpcm"+s+songAbbr+"_vox.wav"));
        	writeAudioFile(zo, vocalsAudio, null);
        }
		NotificationManager.inst().finishMultiSegment("Vocals");

		NotificationManager.inst().startMultiSegment("Bkg", 3);
        if(deriveBackground)
        {
            zo.putNextEntry(new ZipEntry("xadpcm"+s+songAbbr+"_bg.wav"));
            writeAudioFile(zo, vocalsAudio, new SubtractedBkgAudioClip());
        }
        else if(bkgAudio != null)
        {
            zo.putNextEntry(new ZipEntry("xadpcm"+s+songAbbr+"_bg.wav"));
        	writeAudioFile(zo, bkgAudio, null);
        }
		NotificationManager.inst().finishMultiSegment("Bkg");
        
        zo.putNextEntry(new ZipEntry("songs"+s+songAbbr+s+songAbbr+".mid"));

		MidiSystem.write(seq, 1, zo);
		
		zo.close();
		
		NotificationManager.inst().finishMultiSegment(nTitle);
  
	}
	
	private static void writeInputStream(ZipOutputStream o, InputStream i) throws IOException {
		byte [] data = new byte[4096];
		
		int c;

		while((c = i.read(data)) > 0)
		{
			o.write(data,0,c);
		}

		o.flush();
	}

	/**
	 * @return the length of the clip
	 */
	private static void writeAudioFile(ZipOutputStream zo, File file, EffectAudioClip effectClip) throws FileNotFoundException, IOException
	{
		File krWavFile = null;
		
		try {
	        //KR format works best with 16 bit uncompressed files
	        //TODO: use tritonus to avoid loading the entire file into memory at once
			WritableAudioClip clip = new MemAudioClip(2,48000, 16); 
	        
			try {
				SoundRetrievalManager.fillAudioClip(file, clip);
			} catch (UnsupportedAudioFileException e) {
				NotificationManager.inst().error("Can't read audio file?? "+e);
				throw new IllegalArgumentException(e);
			} catch (IOException e) {
				NotificationManager.inst().error("IO error reading audio file "+e);
				throw new IllegalArgumentException(e);
			}
			
			AudioClip outClip;
			
			if(effectClip != null)
			{
				effectClip.setSource(clip);
				outClip = effectClip;
			}
			else outClip  = clip;
	
	        KRWavWriter wavWriter = new KRWavWriter(outClip, krWavFile = File.createTempFile("KR-", ".wav"));
	        
	        wavWriter.writeWav();
	        
	        FileInputStream in = new FileInputStream(krWavFile);
	        
	        byte [] buf = new byte[4096];
	        
	        // Transfer bytes from the file to the ZIP file
	        int len;
	        while ((len = in.read(buf)) > 0) {
	            zo.write(buf, 0, len);
				NotificationManager.inst().progress(-1, "Writing file to zip");
	        }
	
	        zo.closeEntry();    
		}
		finally
		{
			if(krWavFile != null)
				krWavFile.delete();
		}

		NotificationManager.inst().finish();
	}

}
