/*
 * Copyright (C) 2003-2006, C. Ramakrishnan / Illposed Software.
 * All rights reserved.
 *
 * This code is licensed under the BSD 3-Clause license.
 * See file LICENSE (or LICENSE.html) for more information.
 */

package com.illposed.osc;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

import com.illposed.osc.utility.OSCJavaToByteArrayConverter;

/**
 * A bundle represents a collection of OSC packets
 * (either messages or other bundles)
 * and has a time-tag which can be used by a scheduler to execute
 * a bundle in the future,
 * instead of immediately.
 * {@link OSCMessage}s are executed immediately.
 *
 * Bundles should be used if you want to send multiple messages to be executed
 * atomically together, or you want to schedule one or more messages to be
 * executed in the future.
 *
 * @author Chandrasekhar Ramakrishnan
 */
public class OSCBundle extends OSCPacket {

	/**
	 * 2208988800 seconds -- includes 17 leap years
	 */
	public static final BigInteger SECONDS_FROM_1900_TO_1970 =
		new BigInteger("2208988800");

	/**
	 * The Java representation of an OSC timestamp with the semantics of
	 * "immediately".
	 */
	public static final Date TIMESTAMP_IMMEDIATE = new Date(0);

	private Date timestamp;
	private List<OSCPacket> packets;

	/**
	 * Create a new empty OSCBundle with a timestamp of immediately.
	 * You can add packets to the bundle with addPacket()
	 */
	public OSCBundle() {
		this(TIMESTAMP_IMMEDIATE);
	}

	/**
	 * Create an OSCBundle with the specified timestamp.
	 * @param timestamp the time to execute the bundle
	 */
	public OSCBundle(Date timestamp) {
		this((Collection<OSCPacket>) null, timestamp);
	}

	// deprecated since version 1.0, March 2012
	/**
	 * Creates an OSCBundle made up of the given packets
	 * with a timestamp of now.
	 * @param packets array of OSCPackets to initialize this object with
	 * @deprecated
	 */
	public OSCBundle(OSCPacket[] packets) {
		this(packets, TIMESTAMP_IMMEDIATE);
	}

	/**
	 * Creates an OSCBundle made up of the given packets
	 * with a timestamp of now.
	 * @param packets array of OSCPackets to initialize this object with
	 */
	public OSCBundle(Collection<OSCPacket> packets) {
		this(packets, TIMESTAMP_IMMEDIATE);
	}

	// deprecated since version 1.0, March 2012
	/**
	 * Creates an OSCBundle, specifying the packets and timestamp.
	 * @param packets the packets that make up the bundle
	 * @param timestamp the time to execute the bundle
	 * @deprecated
	 */
	public OSCBundle(OSCPacket[] packets, Date timestamp) {
		this((packets == null)
				? new LinkedList<OSCPacket>()
				: Arrays.asList(packets),
				timestamp);
	}

	/**
	 * Create an OSCBundle, specifying the packets and timestamp.
	 * @param packets the packets that make up the bundle
	 * @param timestamp the time to execute the bundle
	 */
	public OSCBundle(Collection<OSCPacket> packets, Date timestamp) {

		if (null == packets) {
			this.packets = new LinkedList<OSCPacket>();
		} else {
			this.packets = new ArrayList<OSCPacket>(packets);
		}
		this.timestamp = timestamp;
		init();
	}

	/**
	 * Return the time the bundle will execute.
	 * @return a Date
	 */
	public Date getTimestamp() {
		return timestamp;
	}

	/**
	 * Set the time the bundle will execute.
	 * @param timestamp Date
	 */
	public void setTimestamp(Date timestamp) {
		this.timestamp = timestamp;
	}

	/**
	 * Add a packet to the list of packets in this bundle.
	 * @param packet OSCMessage or OSCBundle
	 */
	public void addPacket(OSCPacket packet) {
		packets.add(packet);
	}

	/**
	 * Get the packets contained in this bundle.
	 * @return the packets contained in this bundle.
	 */
	public OSCPacket[] getPackets() {
		OSCPacket[] packetArray = new OSCPacket[packets.size()];
		packets.toArray(packetArray);
		return packetArray;
	}

	/**
	 * Convert the time-tag (a Java Date) into the OSC byte stream.
	 * Used Internally.
	 */
	protected void computeTimeTagByteArray(OSCJavaToByteArrayConverter stream) {
		if ((null == timestamp) || (timestamp == TIMESTAMP_IMMEDIATE)) {
			stream.write(0);
			stream.write(1);
			return;
		}

		long millisecs = timestamp.getTime();
		long secsSince1970 = millisecs / 1000;
		long secs = secsSince1970 + SECONDS_FROM_1900_TO_1970.longValue();

		// this line was cribbed from jakarta commons-net's NTP TimeStamp code
		long fraction = ((millisecs % 1000) * 0x100000000L) / 1000;

		stream.write((int) secs);
		stream.write((int) fraction);
	}

	/**
	 * Compute the OSC byte stream representation of the bundle.
	 * Used Internally.
	 * @param stream OscPacketByteArrayConverter
	 */
	protected byte[] computeByteArray(OSCJavaToByteArrayConverter stream) {
		stream.write("#bundle");
		computeTimeTagByteArray(stream);
		byte[] packetBytes;
		for (OSCPacket pkg : packets) {
			packetBytes = pkg.getByteArray();
			stream.write(packetBytes.length);
			stream.write(packetBytes);
		}
		return stream.toByteArray();
	}
}
