/*
 * Copyright (C) 2009 The Sipdroid Open Source Project
 * Copyright (C) 2005 Luca Veltri - University of Parma - Italy
 * 
 * This file is part of Sipdroid (http://www.sipdroid.org)
 * 
 * Sipdroid 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 3 of the License, or
 * (at your option) any later version.
 * 
 * This source code 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 source code; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.sipdroid.media;

import java.io.IOException;
import java.net.SocketException;

import com.beem.project.beem.jingle.JingleService;
import com.beem.project.beem.ui.Call;
import org.sipdroid.net.RtpPacket;
import org.sipdroid.net.RtpSocket;
import org.sipdroid.net.SipdroidSocket;
import org.sipdroid.pjlib.Codec;

import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences.Editor;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.media.ToneGenerator;
import android.os.PowerManager;
import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.provider.Settings;

/**
 * RtpStreamReceiver is a generic stream receiver. It receives packets from RTP
 * and writes them into an OutputStream.
 */
public class RtpStreamReceiver extends Thread {

    /** Whether working in debug mode. */
    public static boolean DEBUG = true;

    /** Payload type */
    int p_type;

    /** Size of the read buffer */
    public static final int BUFFER_SIZE = 1024;

    /** Maximum blocking time, spent waiting for reading new bytes [milliseconds] */
    public static final int SO_TIMEOUT = 200;

    /** The RtpSocket */
    private RtpSocket rtp_socket = null;

    /** Whether it is running */
    private boolean running;
    private AudioManager am;
    private ContentResolver cr;
    public static int speakermode;
    private JingleService mJingle;

    /**
     * Constructs a RtpStreamReceiver.
     * 
     * @param output_stream
     *            the stream sink
     * @param socket
     *            the local receiver SipdroidSocket
     */
    public RtpStreamReceiver(SipdroidSocket socket, int payload_type) {
	init(socket);
	p_type = payload_type;
    }

    /** Inits the RtpStreamReceiver */
    private void init(SipdroidSocket socket) {
	if (socket != null)
	    rtp_socket = new RtpSocket(socket);
    }

    /** Whether is running */
    public boolean isRunning() {
	return running;
    }

    /** Stops running */
    public void halt() {
	running = false;
    }

    public int speaker(int mode) {
	int old = speakermode;

	if (Call.headset > 0 && mode == AudioManager.MODE_NORMAL)
	    return old;
	saveVolume();
	setMode(speakermode = mode);
	restoreVolume();
	return old;
    }

    double smin = 200,s;
    public static int nearend;

    void calc(short[] lin,int off,int len) {
	int i,j;
	double sm = 30000,r;

	for (i = 0; i < len; i += 5) {
	    j = lin[i+off];
	    s = 0.03*Math.abs(j) + 0.97*s;
	    if (s < sm) sm = s;
	    if (s > smin) nearend = 3000/5;
	    else if (nearend > 0) nearend--;
	}
	for (i = 0; i < len; i++) {
	    j = lin[i+off];
	    if (j > 6550)
		lin[i+off] = 6550*5;
	    else if (j < -6550)
		lin[i+off] = -6550*5;
	    else
		lin[i+off] = (short)(j*5);
	}
	r = (double)len/100000;
	smin = sm*r + smin*(1-r);
    }

    static void setStreamVolume(final int stream,final int vol,final int flags) {
	(new Thread() {
	    public void run() {
		AudioManager am = (AudioManager) Call.mContext.getSystemService(Context.AUDIO_SERVICE);
		am.setStreamVolume(stream, vol, flags);
		if (stream == AudioManager.STREAM_MUSIC) restored = true;
	    }
	}).start();
    }

    static boolean restored;

    public static float getEarGain() {
	try {
	    return Float.valueOf(PreferenceManager.getDefaultSharedPreferences(Call.mContext).getString(Call.headset > 0?"heargain":"eargain", "0.25"));
	} catch (NumberFormatException i) {
	    return (float)0.25;
	}			
    }

    void restoreVolume() {
	switch (am.getMode()) {
	    case AudioManager.MODE_IN_CALL:
		setStreamVolume(AudioManager.STREAM_RING,(int)(
		    am.getStreamMaxVolume(AudioManager.STREAM_RING)*
		    getEarGain()), 0);
		track.setStereoVolume(AudioTrack.getMaxVolume()*
		    getEarGain()
		    ,AudioTrack.getMaxVolume()*
		    getEarGain());
		break;
	    case AudioManager.MODE_NORMAL:
		track.setStereoVolume(AudioTrack.getMaxVolume(),AudioTrack.getMaxVolume());
		break;
	}
	setStreamVolume(AudioManager.STREAM_MUSIC,
	    PreferenceManager.getDefaultSharedPreferences(Call.mContext).getInt("volume"+speakermode, 
		am.getStreamMaxVolume(AudioManager.STREAM_MUSIC)*
		(speakermode == AudioManager.MODE_NORMAL?4:3)/4
	    ),0);
    }

    void saveVolume() {
	if (restored) {
	    Editor edit = PreferenceManager.getDefaultSharedPreferences(Call.mContext).edit();
	    edit.putInt("volume"+speakermode,am.getStreamVolume(AudioManager.STREAM_MUSIC));
	    edit.commit();
	}
    }

    void saveSettings() {
	if (!PreferenceManager.getDefaultSharedPreferences(Call.mContext).getBoolean("oldvalid",false)) {
	    int oldvibrate = am.getVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER);
	    int oldvibrate2 = am.getVibrateSetting(AudioManager.VIBRATE_TYPE_NOTIFICATION);
	    if (!PreferenceManager.getDefaultSharedPreferences(Call.mContext).contains("oldvibrate2"))
		oldvibrate2 = AudioManager.VIBRATE_SETTING_ON;
	    int oldpolicy = android.provider.Settings.System.getInt(cr, android.provider.Settings.System.WIFI_SLEEP_POLICY, 
		Settings.System.WIFI_SLEEP_POLICY_DEFAULT);
	    Editor edit = PreferenceManager.getDefaultSharedPreferences(Call.mContext).edit();
	    edit.putInt("oldvibrate", oldvibrate);
	    edit.putInt("oldvibrate2", oldvibrate2);
	    edit.putInt("oldpolicy", oldpolicy);
	    edit.putInt("oldring",am.getStreamVolume(AudioManager.STREAM_RING));
	    edit.putBoolean("oldvalid", true);
	    edit.commit();
	}
    }

    public static void setMode(int mode) {
	Editor edit = PreferenceManager.getDefaultSharedPreferences(Call.mContext).edit();
	edit.putBoolean("setmode", mode != AudioManager.MODE_NORMAL);
	edit.commit();
	AudioManager am = (AudioManager) Call.mContext.getSystemService(Context.AUDIO_SERVICE);
	am.setMode(mode);
    }

    public static void restoreMode() {
	if (PreferenceManager.getDefaultSharedPreferences(Call.mContext).getBoolean("setmode",true)) {
	    setMode(AudioManager.MODE_NORMAL);
	}
    }

    public static void restoreSettings() {
	if (PreferenceManager.getDefaultSharedPreferences(Call.mContext).getBoolean("oldvalid",true)) {
	    AudioManager am = (AudioManager) Call.mContext.getSystemService(Context.AUDIO_SERVICE);
	    ContentResolver cr = Call.mContext.getContentResolver();
	    int oldvibrate = PreferenceManager.getDefaultSharedPreferences(Call.mContext).getInt("oldvibrate",0);
	    int oldvibrate2 = PreferenceManager.getDefaultSharedPreferences(Call.mContext).getInt("oldvibrate2",0);
	    int oldpolicy = PreferenceManager.getDefaultSharedPreferences(Call.mContext).getInt("oldpolicy",0);
	    am.setVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER,oldvibrate);
	    am.setVibrateSetting(AudioManager.VIBRATE_TYPE_NOTIFICATION,oldvibrate2);
	    Settings.System.putInt(cr, Settings.System.WIFI_SLEEP_POLICY, oldpolicy);
	    setStreamVolume(AudioManager.STREAM_RING, PreferenceManager.getDefaultSharedPreferences(Call.mContext).getInt("oldring",0), 0);
	    Editor edit = PreferenceManager.getDefaultSharedPreferences(Call.mContext).edit();
	    edit.putBoolean("oldvalid", false);
	    edit.commit();
	    PowerManager pm = (PowerManager) Call.mContext.getSystemService(Context.POWER_SERVICE);
	    PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK |
		PowerManager.ACQUIRE_CAUSES_WAKEUP, "Sipdroid.RtpStreamReceiver");
	    wl.acquire(1000);
	}
	restoreMode();
    }

    public static float good, late, lost, loss;
    public static int timeout;

    void empty() {
	try {
	    rtp_socket.getDatagramSocket().setSoTimeout(1);
	    for (;;)
		rtp_socket.receive(rtp_packet);
	} catch (SocketException e2) {
	    e2.printStackTrace();
	} catch (IOException e) {
	}
	try {
	    rtp_socket.getDatagramSocket().setSoTimeout(1000);
	} catch (SocketException e2) {
	    e2.printStackTrace();
	}
    }

    RtpPacket rtp_packet;
    AudioTrack track;

    /** Runs it in a new Thread. */
    public void run() {
	boolean nodata = PreferenceManager.getDefaultSharedPreferences(Call.mContext).getBoolean("nodata",false);

	if (rtp_socket == null) {
	    if (DEBUG)
		println("ERROR: RTP socket is null");
	    return;
	}

	byte[] buffer = new byte[BUFFER_SIZE+12];
	byte[] buffer_gsm = new byte[33+12];
	int i;
	rtp_packet = new RtpPacket(buffer, 0);

	if (DEBUG)
	    println("Reading blocks of max " + buffer.length + " bytes");

	running = true;
	speakermode = Call.docked > 0?AudioManager.MODE_NORMAL:AudioManager.MODE_IN_CALL;
	restored = false;

	android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO);
	am = (AudioManager) Call.mContext.getSystemService(Context.AUDIO_SERVICE);
	cr = Call.mContext.getContentResolver();
	saveSettings();
	Settings.System.putInt(cr, Settings.System.WIFI_SLEEP_POLICY,Settings.System.WIFI_SLEEP_POLICY_NEVER);
	am.setVibrateSetting(AudioManager.VIBRATE_TYPE_RINGER,AudioManager.VIBRATE_SETTING_OFF);
	am.setVibrateSetting(AudioManager.VIBRATE_TYPE_NOTIFICATION,AudioManager.VIBRATE_SETTING_OFF);
	int oldvol = am.getStreamVolume(AudioManager.STREAM_MUSIC);
	track = new AudioTrack(AudioManager.STREAM_MUSIC, 8000, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT,
	    BUFFER_SIZE*2*2, AudioTrack.MODE_STREAM);
	short lin[] = new short[BUFFER_SIZE];
	short lin2[] = new short[BUFFER_SIZE];
	int user, server, lserver, luser, cnt, todo, headroom, len = 0, seq = 0, cnt2 = 0, m = 1,
	expseq, getseq, vm = 1, gap, gseq;
	timeout = 1;
	boolean islate;
	user = 0;
	lserver = 0;
	luser = -8000;
	cnt = 0;
	switch (p_type) {
	    case 3:
		Codec.init();
		break;
	    case 0:
	    case 8:
		G711.init();
		break;
	}
	ToneGenerator tg = new ToneGenerator(AudioManager.STREAM_MUSIC,(int)(ToneGenerator.MAX_VOLUME*2*0.95));
	track.play();
	if (Call.headset > 0 && Call.oRingtone != null) {
	    ToneGenerator tg2 = new ToneGenerator(AudioManager.STREAM_RING,(int)(ToneGenerator.MAX_VOLUME*2*0.95));
	    tg2.startTone(ToneGenerator.TONE_SUP_RINGTONE);
	    System.gc();
	    tg2.stopTone();
	} else
	    System.gc();
	while (running) {
	    if (Call.call_state == Call.UA_STATE_HOLD) {
		tg.stopTone();
		track.pause();
		while (running && Call.call_state == Call.UA_STATE_HOLD) {
		    try {
			sleep(1000);
		    } catch (InterruptedException e1) {
			e1.printStackTrace();
		    }
		}
		track.play();
		System.gc();
		timeout = 1;
		seq = 0;
	    }
	    try {
		rtp_socket.receive(rtp_packet);
		if (timeout != 0) {
		    tg.stopTone();
		    track.pause();
		    user += track.write(lin2,0,BUFFER_SIZE);
		    user += track.write(lin2,0,BUFFER_SIZE);
		    track.play();
		    cnt += 2*BUFFER_SIZE;
		    empty();
		}
		timeout = 0;
	    } catch (IOException e) {
		if (timeout == 0 && nodata) {
		    tg.startTone(ToneGenerator.TONE_SUP_RINGTONE);
		}
		rtp_socket.getDatagramSocket().disconnect();
		if (++timeout > 22) {
		    try {
			mJingle.closeCall();
		    } catch (RemoteException e1) {
			e1.printStackTrace();
		    }
		    break;
		}
	    }
	    if (running && timeout == 0) {		
		gseq = rtp_packet.getSequenceNumber();
		if (seq == gseq) {
		    m++;
		    continue;
		}

		server = track.getPlaybackHeadPosition();
		headroom = user-server;

		if (headroom > 1500)
		    cnt += len;
		else
		    cnt = 0;

		if (lserver == server)
		    cnt2++;
		else
		    cnt2 = 0;

		if (cnt <= 500 || cnt2 >= 2 || headroom - 875 < len) {
		    switch (rtp_packet.getPayloadType()) {
			case 0:
			    len = rtp_packet.getPayloadLength();
			    G711.ulaw2linear(buffer, lin, len);
			    break;
			case 8:
			    len = rtp_packet.getPayloadLength();
			    G711.alaw2linear(buffer, lin, len);
			    break;
			case 3:
			    for (i = 12; i < 45; i++)
				buffer_gsm[i] = buffer[i];
			    len = Codec.decode(buffer_gsm, lin, 0);
			    break;
		    }

		    if (speakermode == AudioManager.MODE_NORMAL)
			calc(lin,0,len);
		}

		if (headroom < 250) { 
		    todo = 875 - headroom;
		    println("insert "+todo);
		    islate = true;
		    user += track.write(lin2,0,todo);
		} else
		    islate = false;

		if (cnt > 500 && cnt2 < 2) {
		    todo = headroom - 875;
		    println("cut "+todo);
		    if (todo < len)
			user += track.write(lin,todo,len-todo);
		} else
		    user += track.write(lin,0,len);

		seq = gseq;

		if (user >= luser + 8000 && Call.call_state == Call.UA_STATE_INCALL) {
		    if (luser == -8000 || am.getMode() != speakermode) {
			saveVolume();
			setMode(speakermode);
			restoreVolume();
		    }
		    luser = user;
		}
		lserver = server;
	    }
	}
	track.stop();
	saveVolume();
	setStreamVolume(AudioManager.STREAM_MUSIC,oldvol,0);
	restoreSettings();
	setStreamVolume(AudioManager.STREAM_MUSIC,oldvol,0);
	tg.stopTone();
	tg = new ToneGenerator(AudioManager.STREAM_RING,ToneGenerator.MAX_VOLUME/4*3);
	tg.startTone(ToneGenerator.TONE_PROP_PROMPT);
	try {
	    sleep(500);
	} catch (InterruptedException e) {
	    e.printStackTrace();
	}
	tg.stopTone();

	rtp_socket.close();
	rtp_socket = null;

	if (DEBUG)
	    println("rtp receiver terminated");
    }

    /** Debug output */
    private static void println(String str) {
	System.out.println("RtpStreamReceiver: " + str);
    }

    public static int byte2int(byte b) { // return (b>=0)? b : -((b^0xFF)+1);
	// return (b>=0)? b : b+0x100;
	return (b + 0x100) % 0x100;
    }

    public static int byte2int(byte b1, byte b2) {
	return (((b1 + 0x100) % 0x100) << 8) + (b2 + 0x100) % 0x100;
    }
}
