/*
 * Track.java
 *
 * Copyright (c) 2007  Vreixo Formoso
 *
 * This library 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.
 * See COPYING file for details.
 */

package org.pykix.libburnia.libburn;

import java.util.EnumSet;

import org.pykix.libburnia.bindings.Proxy;

/**
 * References a single track on a disc.
 * 
 * @author Vreixo Formoso
 * @since 0.1
 */
public class Track extends Proxy {

	/** wether is needed to free or not the track when no more needed */
	private boolean free;
	
	/**
	 * Create a new Track, to use in TAO recording or to put in a
	 * {@link Session}
	 */
	public Track() {
		super( burn_track_create() );
		free = true;
	}
	
	/* package protected */
	Track(long ptr) {
		super(ptr);
		free = false;
	}
	
	/**
	 * Define the data in a track.
	 * 
	 * @param offset
	 * 		The number of 0s to write before start of data.
	 * @param tail
	 * 		The number of extra 0s to write after data.
	 * @param pad
	 * 		If <code>true</code>, the lib should pad the last sector with 0s 
	 * 		if the track isn't exactly sector sized.  (otherwise the lib will
	 * 		begin reading from the next track)
	 * @param mode
	 * 		data format
	 */
	public void defineData(int offset, int tail, boolean pad, 
			EnumSet<BurnMode> mode) {
		
		int modebm = 0;
		for ( BurnMode m : mode ) {
			modebm |= m.bm;
		}
		
		burn_track_define_data( pointerOf(this), offset, tail, pad, modebm );
	}
	
	/**
	 * Define wether a track shall swap bytes of its input stream.
	 * 
	 * @param swapSourceBytes
	 * 		<code>false</code> to do not swap, <code>true</code> to swap byte 
	 * 		pairs
	 */
	public void setByteSwap(boolean swapSourceBytes) {
		burn_track_set_byte_swap( pointerOf(this), swapSourceBytes ? 1 : 0);
	}
	
	/**
	 * Set the ISRC details for the track.
	 * 
	 * @param country
	 * 		the 2 char country code. Each character must be only numbers or 
	 * 		letters.
	 * @param owner
	 * 		3 char owner code. Each character must be only numbers
	 * 		or letters.
	 * @param year
	 * 		2 digit year. A number in 0-99 (Yep, not Y2K friendly).
	 * @param serial
	 * 		5 digit serial number. A number in 0-99999.
	 * @throws IllegalArgumentException
	 * 		If some of restrictions given above are wrong.
	 * 
	 * TODO add more restriction checks in java code. We need to check that
	 * 		characters are valid.
	 */
	public void setIsrc(String country, String owner, int year, int serial) {
		
		if ( country.length() != 2 ) {
			throw new IllegalArgumentException("Country must be 2 characters " +
					"length");
		}
		
		if ( owner.length() != 3 ) {
			throw new IllegalArgumentException("owner must be 3 characters " +
					"length");
		}
		
		if ( (year > 99) || (year < 0) ) {
			throw new IllegalArgumentException("year must be a number in 0-99");
		}
		
		if ( (serial > 99999) || (serial < 0) ) {
			throw new IllegalArgumentException("99999 must be a number in " +
					"0-99999");
		}
		
		burn_track_set_isrc( pointerOf(this), country, owner, (byte)year, serial );
		
	}
	
	/**
	 * Disable ISRC parameters for the track.
	 */
	public void clearIsrc() {
		burn_track_clear_isrc( pointerOf(this) );
	}
	
	/**
	 * Set the track's data source.
	 * 
	 * @param src
	 * 		The data source to use for the contents of the track.
	 * @return
	 * 		A status code stating if the source is ready for use for
	 * 		writing the track, or if an error occured
	 */
	public SourceStatus setSource(BurnSource src) {
		
		long ptr;
		if (src instanceof Proxy) {
			Proxy source = (Proxy) src;
			ptr = pointerOf(source);
		} else {
			//FIXME add support for java-based sources
			throw new ClassCastException("Only Proxy sources supported");
		}
		
		int s = burn_track_set_source( pointerOf(this), ptr);
		switch (s) {
		case 0:
			return SourceStatus.OK;
		case 1:
			return SourceStatus.EOF;
		case 2:
			return SourceStatus.FAILED;
		default:
			throw new RuntimeException("Unexpected return state");
		}
	}
	
	/**
	 * Set a default track size to be used only if the track turns out to be of
	 * unpredictable length and if the effective write type demands a fixed 
	 * size.
	 * 
	 * <p>
	 * This can be useful to enable write types CD SAO or DVD DAO together with
	 * a track source like stdin. If the track source delivers fewer bytes than
	 * announced then the track will be padded up with zeros.
	 * 
	 * @param size
	 * 		The size to set in bytes
	 * @return
	 * 		<code>true</code> on success, <code>false</code> if failure
	 */
	public boolean setDefaultSize(long size) {
		return burn_track_set_default_size( pointerOf(this), size);
	}
	
	/**
	 * Tells how long the track will be on disc.
	 * <i>Not reliable with tracks of undefined length. Should never
	 * be called in this case.</i>
	 * 
	 * @return
	 * 		Number of sectors the track will need.
	 */
	public int getSectors() {
		return burn_track_get_sectors( pointerOf(this) );
	}
	
	/**
	 * Gets a copy of the {@link TocEntry} structure associated with the track.
	 * 
	 * @return
	 * 		A {@link TocEntry} object.
	 */
	public TocEntry getEntry() {
		return burn_track_get_entry( pointerOf(this) );
	}
	
	/**
	 * Gets the mode of the track.
	 * 
	 * @return
	 * 		The track's mode
	 */
	public EnumSet<BurnMode> getMode() {
		
		int modebm = burn_track_get_mode( pointerOf(this) );
		
		EnumSet<BurnMode> mode = EnumSet.noneOf(BurnMode.class);
		
		for ( BurnMode m : EnumSet.allOf(BurnMode.class) ) {
			
			if ( (modebm & m.bm) != 0 ) {
				mode.add(m);
			}
		}
		
		return mode;
	}
	
	/**
	 * Tells how many source bytes have been read by the track during burn.
	 * 
	 * @return
	 * 		Bytes already read.
	 */
	public long getReadBytes() {
		return burn_track_get_read_bytes( pointerOf(this) );
	}
	
	/**
	 * Tells how many source bytes have been written by the track during burn.
	 * 
	 * @return
	 * 		Bytes already written.
	 */
	public long getWrittenBytes() {
		return burn_track_get_written_bytes( pointerOf(this) );
	}
	
	@Override
	protected void finalize() throws Throwable {
		super.finalize();
		
		/*
		 * When retrieved from Session.getTrack we haven't to
		 * free the C object because we don't own a reference
		 */
		if ( free ) {
			burn_track_free( pointerOf(this) );
		}
	}
	
	private static native long burn_track_create();
	
	private static native void burn_track_free(long t);
	
	private static native void burn_track_define_data(long t, int offset, 
			int tail, boolean pad, int mode);
	
	private static native int burn_track_set_byte_swap(long t, 
			int swapSourceBytes);
	
	private static native void burn_track_set_isrc(long t, String country, 
			String owner, byte year, int serial);
	
	private static native void burn_track_clear_isrc(long t);
	
	private static native int burn_track_set_source(long t, long s);
	
	private static native boolean burn_track_set_default_size(long t, long size);
	
	private static native int burn_track_get_sectors(long t);
	
	private static native TocEntry burn_track_get_entry(long t);
	
	private static native int burn_track_get_mode(long track);
	
	/*
	 * The two methods are the java version of the single libburn method
	 * int burn_track_get_counters(struct burn_track *t, 
	 *     off_t *read_bytes, off_t *written_bytes);
	 */
	private static native long burn_track_get_read_bytes(long t);
	private static native long burn_track_get_written_bytes(long t);
	
}
