/*
 * Copyright 2007 Sun Microsystems, Inc.
 *
 * This file is part of jVoiceBridge.
 *
 * jVoiceBridge 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 and distributed hereunder
 * to you.
 *
 * jVoiceBridge 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 program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Sun designates this particular file as subject to the "Classpath"
 * exception as provided by Sun in the License file that accompanied this
 * code.
 */

package com.sun.voip.server;

import com.sun.voip.CallEvent;
import com.sun.voip.CallEventListener;
import com.sun.voip.CallState;
import com.sun.voip.Logger;

import java.io.IOException;

import org.voicebridge.Config;

/**
 * Listen for incoming calls, play treatments to prompt the caller for
 * meeting id and pass code.  Transfer the call to the appropriate meeting.
 */
public class IncomingConferenceHandler extends Thread
	implements CallEventListener {

    private String meetingCode = "";
    private String passCode = "";

    private int state = WAITING_FOR_MEETING_CODE;

    private static final int WAITING_FOR_MEETING_CODE = 1;
    private static final int WAITING_FOR_PASS_CODE    = 2;
    private static final int IN_MEETING               = 3;

    private static final String ENTER_MEETING_CODE =
	"enter-conf-call-number.au;then-press-pound.au" ;

    private static final String INVALID_MEETING_CODE = "conf-invalid.au";

    private static final String INVALID_PASS_CODE = "bad_user_id_1.au"; // "badcode.au";

    private static final String INCOMING_TIMEOUT = "incoming_timeout.au";

    private static final String ENTER_REQUIRED_PASS_CODE =
	"please-enter-your.au;access-code.au;then-press-pound.au";

    private static final String LEAVE_MEETING = "leaveCLICK.au";

    private static final String CALL_MUTED = "conf-muted.au";

    private static final String CALL_UNMUTED = "conf-unmuted.au";

    private static final String CALLER_NUMBER = "you-are-caller-number.au";

    private static final int DEFAULT_TIMEOUT = 30000;
    private static int timeout = DEFAULT_TIMEOUT;

    private IncomingCallHandler incomingCallHandler;

    private String lastDtmfKey;
    private String phoneNo;

    /**
     * Constructor.
     */
    public IncomingConferenceHandler(IncomingCallHandler incomingCallHandler, String phoneNo) {
		this.incomingCallHandler = incomingCallHandler;
		this.phoneNo = phoneNo;

		incomingCallHandler.addCallEventListener(this);

	    Logger.println("IncomingConferenceHandler:  " + phoneNo);
    }

    private String lastMessagePlayed;

    private void playTreatmentToCall(String treatment) {
        try {
	    incomingCallHandler.playTreatmentToCall(treatment);
        } catch (IOException e) {
            Logger.println("Call " + incomingCallHandler
                + " Can't play treatment " + treatment);
        }
        lastMessagePlayed = treatment;
    }

    private void playConferenceId() {
	if (meetingCode == null || meetingCode.length() == 0) {
	    return;
	}

	String s = "conference.au";

	for (int i = 0; i < meetingCode.length(); i++) {
	    s += ";" + meetingCode.substring(i, i + 1) + ".au";
	}

	playTreatmentToCall(s);
    }

    private void playNumberOfCalls() {
	String s = incomingCallHandler.getNumberOfCallsAsTreatment();
	playTreatmentToCall(s + ";conf-peopleinconf.au");
    }

    /*
     * Called when status for an incoming call changes.
     */
    public void callEventNotification(CallEvent callEvent) {
	//if (Logger.logLevel >= Logger.LOG_INFO) {
	    Logger.println("IncomingConferenceHandler " + callEvent.toString());
	//}

	if (callEvent.equals(CallEvent.STATE_CHANGED) &&
		callEvent.getCallState().equals(CallState.ESTABLISHED)) {

	    /*
	     * New incoming call
	     */
	    if (callEvent.getInfo() != null) {
	    	Logger.println("IncomingConferenceHandler:  " + callEvent.getInfo());
	    }

		if (Config.getInstance().getMeetingCode(phoneNo) != null)
		{
			meetingCode = Config.getInstance().getMeetingCode(phoneNo);

	    	Logger.println("IncomingConferenceHandler:  meeting code " + meetingCode);

			if (Config.getInstance().getPassCode(meetingCode, phoneNo) == null)
			{
				try {
					incomingCallHandler.transferCall(meetingCode);
					state = IN_MEETING;

				} catch (IOException e) {
					System.err.println("Exception joining meeting! " + meetingCode + " " + e.getMessage());
				}

			} else {
				state = WAITING_FOR_PASS_CODE;
				playTreatmentToCall(ENTER_REQUIRED_PASS_CODE);
				start();
			}

		} else {

	    	playTreatmentToCall(ENTER_MEETING_CODE);
			state = WAITING_FOR_MEETING_CODE;
			start();
		}

	    return;
	}

	if (callEvent.equals(CallEvent.STATE_CHANGED) &&
		callEvent.getCallState().equals(CallState.ENDED)) {

	    playTreatmentToCall(LEAVE_MEETING);
	    return;
	}

	/*
	 * We're only interested in dtmf keys
	 */
        if (callEvent.equals(CallEvent.DTMF_KEY) == false) {
	    return;
	}

        String dtmfKey = callEvent.getDtmfKey();

	if (state == WAITING_FOR_MEETING_CODE) {
	    getMeetingCode(dtmfKey);
	} else if (state == WAITING_FOR_PASS_CODE) {
	    getPassCode(dtmfKey);
	} else {
	    if (lastDtmfKey.equals("*")) {
		if (dtmfKey.equals("1")) {
		    incomingCallHandler.setMuted(true);
		    playTreatmentToCall(CALL_MUTED);
		} else if (dtmfKey.equals("2")) {
		    incomingCallHandler.setMuted(false);
		    playTreatmentToCall(CALL_UNMUTED);
		} else if (dtmfKey.equals("9")) {
		    playConferenceId();
		} else if (dtmfKey.equals("#")) {
		    playNumberOfCalls();
		}
	    }
	}

	lastDtmfKey = dtmfKey;
    }

    private void getMeetingCode(String dtmfKey) {
        if (!dtmfKey.equals("#")) {
            meetingCode += dtmfKey;  // accumulate meeting code
            return;
        }

		if (meetingCode.length() == 0) {
			playTreatmentToCall(INVALID_MEETING_CODE
			+ ";" + ENTER_MEETING_CODE);
			return;
		}

		String confRoom = null;

		if (Config.getInstance().isValidConference(meetingCode))
		{
			confRoom = meetingCode;

		} else if (Config.getInstance().isValidConferenceExten(meetingCode))	{

			confRoom = Config.getInstance().getMeetingCode(meetingCode);

		}

		if (confRoom != null)
		{

			if (Config.getInstance().getPassCode(confRoom, phoneNo) == null)
			{
				try {
					incomingCallHandler.transferCall(confRoom);
					state = IN_MEETING;

				} catch (IOException e) {
					System.err.println("Exception joining meeting! " + meetingCode + " " + e.getMessage());
				}

			} else {
        		state = WAITING_FOR_PASS_CODE;
        		playTreatmentToCall(ENTER_REQUIRED_PASS_CODE);
        		return;
			}

		} else {
			playTreatmentToCall(INVALID_MEETING_CODE + ";" + ENTER_MEETING_CODE);
			meetingCode = "";
			state = WAITING_FOR_MEETING_CODE;
			return;
		}
    }

    private void getPassCode(String dtmfKey) {
        if (!dtmfKey.equals("#")) {
	    passCode += dtmfKey;   // accumulate pass code
	    return;
		}

		/*
		 * For now, allow people to join without a passCode
		 */
		int intPassCode = 0;

		if (passCode.length() > 0) {
			try {
				intPassCode = Integer.parseInt(passCode);

			} catch (NumberFormatException e) {
					playTreatmentToCall(INVALID_PASS_CODE);

				passCode = "";
				return;
			}
		}

		if (Config.getInstance().isValidConferencePin(meetingCode, passCode))
		{
			try {
				incomingCallHandler.transferCall(meetingCode);
				state = IN_MEETING;

			} catch (IOException e) {
				System.err.println("Exception joining meeting! " + meetingCode + " " + e.getMessage());

				playTreatmentToCall(INVALID_PASS_CODE);
				passCode = "";
				state = WAITING_FOR_PASS_CODE;
			}

		} else {

			playTreatmentToCall(INVALID_PASS_CODE);
			passCode = "";
			state = WAITING_FOR_PASS_CODE;
		}
    }

    public void run() {
	/*
	 * Timeout handler to re-prompt user
	 */
	long startTime = System.currentTimeMillis();

	while (state == WAITING_FOR_MEETING_CODE ||
	        state == WAITING_FOR_PASS_CODE) {

	    int currentState = state;

	    try {
		Thread.sleep(timeout);

		if (state != WAITING_FOR_MEETING_CODE &&
			state != WAITING_FOR_PASS_CODE) {

		    break;
		}

		if (currentState == state) {
		    if (System.currentTimeMillis() - startTime >=
			    CallSetupAgent.getDefaultCallAnswerTimeout() * 1000) {

			playTreatmentToCall(INCOMING_TIMEOUT);

			/* We'd like to wait until the treatment is done
			 * before cancelling the call.
			 */
			try {
			    Thread.sleep(5000);
			} catch (InterruptedException e) {
			}

	    		incomingCallHandler.cancelRequest(
			    "Incoming call timeout");

			break;
		    }

		    playTreatmentToCall(lastMessagePlayed);
		}
	    } catch (InterruptedException e) {
		Logger.println("Incoming ConferenceHandler Interrupted!");
	    }
	}
    }

}
