/*
 * 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.CallParticipant;
import com.sun.voip.CallEvent;
import com.sun.voip.CallState;
import com.sun.voip.Logger;
import com.sun.voip.CallEventListener;

public class CallMigrator extends Thread {
    CallEventListener requestHandler;
    CallParticipant cp;

    public CallMigrator(CallEventListener requestHandler, CallParticipant cp) {
    this.requestHandler = requestHandler;
    this.cp = cp;
    }

    /*
     * Migrate a call.  Set up the new call, join the conference and
     * terminate the original call.
     */
    public void run() {
    String callId = cp.getCallId();  // call to migrate

    CallHandler callHandler = CallHandler.findMigratingCall(callId);

    if (callHandler != null) {
        /*
         * Call migration is in progess.
         * Cancel previous migration request.
         */
         callHandler.cancelRequest("new migration requested");
    }

    CallHandler previousCall = CallHandler.findCall(callId);

    if (previousCall == null) {
        if (cp.getConferenceId() == null) {
            Logger.println("Call migrator can't find call Id " + callId);
        return;
        }

        /*
         * Treat it like a new call
         */
        migrateWithNoPreviousCall(requestHandler, cp);
        return;
    }

if (false) {
    if (previousCall.isCallEstablished() == false) {
        Logger.println(
        "Call migrator can't migrate call which is not established");

        return;
    }
}

    CallParticipant previousCp = previousCall.getCallParticipant();

    String previousEndTreatment = previousCp.getCallEndTreatment();
        String previousLeaveTreatment = previousCp.getConferenceLeaveTreatment();

    previousCp.setCallEndTreatment(null);
    previousCp.setConferenceLeaveTreatment(null);

    cp.setMuted(previousCp.isMuted());	// preserve mute

    Logger.println("Call migrating " + previousCp + " preserving mute "
        + previousCp.isMuted());

    if (cp.getConferenceId() == null) {
        cp.setConferenceId(previousCp.getConferenceId());
    }

    if (cp.getName() == null) {
        cp.setName(previousCp.getName());
    }

    /*
     * SecondPartyNumber is the new number to call.
     * If it starts with "Id-", it's a callId of
     * a call already in progress.  Otherwise, it's a phone number to call.
     */
    String secondParty = cp.getSecondPartyNumber();

    if (secondParty.indexOf("Id-") == 0) {
        callHandler = CallHandler.findCall(secondParty.substring(3));

        if (callHandler == null) {
        Logger.println("Can't find existing call to " + secondParty);
        //Logger.logLevel = logLevel;
        return;
        }

        cp = callHandler.getCallParticipant();
        cp.setCallId(previousCp.getCallId());
    } else {
        /*
         * Invite the secondParty if call is not already setup.
         * After the party answers terminate the previous call.
         */
        cp.setPhoneNumber(cp.getSecondPartyNumber());
        cp.setConferenceJoinTreatment(null);

        /*
         * Use the request handler from the previous call (if there
         * is one) so that status changes will be sent to the same socket
         * as the previous call.  This will make the migration seemless.
         */
        CallEventListener previousCallEventListener = (CallEventListener) previousCall.getRequestHandler();

        if (previousCallEventListener != null) {
        requestHandler = previousCallEventListener;
        }

        /*
         * Previous call is migrating.  It will probably be
         * common for someone to hangup their cell phone before
         * answering the new call.  In this case we need to
         * suppress status from the previous call.
         * However, if the previous call ends and then migration fails,
         * we need to send call ending status back.
         */
        previousCall.suppressStatus = true;

        OutgoingCallHandler newCall =
        new OutgoingCallHandler(requestHandler, cp);

        previousCall.getMember().migrating();

        synchronized(this) {
            newCall.suppressStatus = true;

            newCall.start();		// call new party

            /*
             * Wait for call to be established
             */
            if (newCall.waitForCallToBeEstablished() == false) {
            String reason = newCall.getReasonCallEnded();

            Logger.println("Migration failed: " + reason);

            previousCp.setConferenceLeaveTreatment(previousLeaveTreatment);
            previousCp.setCallEndTreatment(previousEndTreatment);
            CallEvent callEvent = new CallEvent(CallEvent.STATE_CHANGED);

            callEvent.setCallState(new CallState(CallState.ENDED));

            callEvent.setInfo("Migration failed: " + reason);

            newCall.suppressStatus = false;
            newCall.sendCallEventNotification(callEvent);

            previousCall.suppressStatus = false;

            if (!previousCall.isCallEstablished()) {
                previousCall.sendCallEventNotification(callEvent);
            }

            //Logger.logLevel = logLevel;
                return;
            }

            newCall.suppressStatus = false;
        }

        if (previousCall.isCallEstablished() == true) {
        Logger.println("migrate mix descriptors for " + previousCall);
            newCall.getMember().migrate(previousCall.getMember());
        } else {
        Logger.println("migrate:  previous call is not established "
            + previousCall);
        }
    }

    Logger.println("Call " + previousCp
        + " migrated to " + cp.getPhoneNumber());

    previousCall.suppressStatus = false;
    cp.setMigrateCall(false);	// call is no longer migrating

    /*
     * If the previous call ended early, we need to re-add
     * it as a call status listener so that it will get the MIGRATED status
     */
    if (!previousCall.isCallEstablished()) {
        previousCall.addCallEventListener(previousCall.getRequestHandler());
    }

    CallEvent callEvent = new CallEvent(CallEvent.MIGRATED);

    callEvent.setInfo(previousCp + " migrated to " + cp);

    previousCall.sendCallEventNotification(callEvent);

    previousCall.suppressStatus = true;

    /*
     * Suppress conference leave treatment.  No other calls should
     * know that this call migrated.
     */
    previousCp.setCallEndTreatment(null);
    previousCp.setConferenceLeaveTreatment(null);

    previousCall.cancelRequest("Call " + previousCp
        + " migrated to " + cp.getPhoneNumber());
    }

    private void migrateWithNoPreviousCall(CallEventListener requestHandler,
        CallParticipant cp) {

        cp.setPhoneNumber(cp.getSecondPartyNumber());

    OutgoingCallHandler callHandler = new OutgoingCallHandler(requestHandler, cp);

    synchronized(this) {
        callHandler.start();		// call new party

        /*
         * Wait for call to be established
         */
        if (callHandler.waitForCallToBeEstablished() == false) {
            return;
        }
    }

    cp.setMigrateCall(false);	// call is no longer migrating

    CallEvent callEvent = new CallEvent(CallEvent.MIGRATED);

    callEvent.setInfo("migrated to " + cp);

    callHandler.sendCallEventNotification(callEvent);
    }

    /*
     * Cancel a migration request
     * This simply cancels the new call before it's been answered.
     */
    public static void hangup(String callId, String reason) {
    /*
     * Find the new call being migrated to and cancel that call.
     */
    CallHandler callHandler = CallHandler.findMigratingCall(callId);

    if (callHandler == null) {
        Logger.println("Can't find migrating call for " + callId);
        return;
        }

    callHandler.cancelRequest(reason);
    }

}
