/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.services.telephony;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import android.telecomm.Conference;
import android.telecomm.Connection;

import com.android.internal.telephony.Call;

/**
 * Maintains a list of all the known GSM connections and implements GSM-specific conference
 * call functionality.
 */
final class GsmConferenceController {
    private final Connection.Listener mConnectionListener = new Connection.Listener() {
                @Override
                public void onStateChanged(Connection c, int state) {
                    recalculate();
                }

                /** ${inheritDoc} */
                @Override
                public void onDisconnected(Connection c, int cause, String message) {
                    recalculate();
                }
            };

    /** The known GSM connections. */
    private final List<GsmConnection> mGsmConnections = new ArrayList<>();

    private final TelephonyConnectionService mConnectionService;

    public GsmConferenceController(TelephonyConnectionService connectionService) {
        mConnectionService = connectionService;
    }

    /** The GSM conference connection object. */
    private Conference mGsmConference;

    void add(GsmConnection connection) {
        mGsmConnections.add(connection);
        connection.addConnectionListener(mConnectionListener);
        recalculate();
    }

    void remove(GsmConnection connection) {
        connection.removeConnectionListener(mConnectionListener);
        mGsmConnections.remove(connection);
        recalculate();
    }

    private void recalculate() {
        recalculateConferenceable();
        recalculateConference();
    }

    /**
     * Calculates the conference-capable state of all GSM connections in this connection service.
     */
    private void recalculateConferenceable() {
        Log.v(this, "recalculateConferenceable : %d", mGsmConnections.size());

        List<Connection> activeConnections = new ArrayList<>(mGsmConnections.size());
        List<Connection> backgroundConnections = new ArrayList<>(mGsmConnections.size());

        // Loop through and collect all calls which are active or holding
        for (GsmConnection connection : mGsmConnections) {
            com.android.internal.telephony.Connection radioConnection =
                    connection.getOriginalConnection();
            Log.d(this, "recalc - %s %s",
                    radioConnection == null ? null : radioConnection.getState(), connection);

            if (radioConnection != null) {
                switch(radioConnection.getState()) {
                    case ACTIVE:
                        activeConnections.add(connection);
                        break;
                    case HOLDING:
                        backgroundConnections.add(connection);
                        break;
                    default:
                        connection.setConferenceableConnections(
                                Collections.<Connection>emptyList());
                        break;
                }
            }
        }

        Log.v(this, "active: %d, holding: %d",
                activeConnections.size(), backgroundConnections.size());

        // Go through all the active connections and set the background connections as
        // conferenceable.
        for (Connection connection : activeConnections) {
            connection.setConferenceableConnections(backgroundConnections);
        }

        // Go through all the background connections and set the active connections as
        // conferenceable.
        for (Connection connection : backgroundConnections) {
            connection.setConferenceableConnections(activeConnections);
        }
        // TODO: Do not allow conferencing of already conferenced connections.
    }

    private void recalculateConference() {
        Set<GsmConnection> conferencedConnections = new HashSet<>();

        for (GsmConnection connection : mGsmConnections) {
            com.android.internal.telephony.Connection radioConnection =
                connection.getOriginalConnection();

            if (radioConnection != null) {
                Call.State state = radioConnection.getState();
                Call call = radioConnection.getCall();
                if ((state == Call.State.ACTIVE || state == Call.State.HOLDING) &&
                        (call != null && call.isMultiparty())) {
                    conferencedConnections.add(connection);
                }
            }
        }

        Log.d(this, "Recalculate conference calls %s %s.",
                mGsmConference, conferencedConnections);

        if (conferencedConnections.size() < 2) {
            Log.d(this, "less than two conference calls!");
            // No more connections are conferenced, destroy any existing conference.
            if (mGsmConference != null) {
                Log.d(this, "with a conference to destroy!");
                mGsmConference.destroy();
                mGsmConference = null;
            }
        } else {
            if (mGsmConference != null) {
                List<Connection> existingConnections = mGsmConference.getConnections();
                // Remove any that no longer exist
                for (Connection connection : existingConnections) {
                    if (!conferencedConnections.contains(connection)) {
                        mGsmConference.removeConnection(connection);
                    }
                }

                // Add any new ones
                for (Connection connection : conferencedConnections) {
                    if (!existingConnections.contains(connection)) {
                        mGsmConference.addConnection(connection);
                    }
                }
            } else {
                mGsmConference = new GsmConference(null);
                for (Connection connection : conferencedConnections) {
                    Log.d(this, "Adding a connection to a conference call: %s %s",
                            mGsmConference, connection);
                    mGsmConference.addConnection(connection);
                }
                mConnectionService.addConference(mGsmConference);
            }
        }
    }
}

@@ -6911,32 +6911,517 @@
 );%0A            %7D
+%0A%0A            // Set the conference state to the same state as its child connections.%0A            Connection conferencedConnection = mGsmConference.getConnections().get(0);%0A            switch (conferencedConnection.getState()) %7B%0A                case Connection.STATE_ACTIVE:%0A                    mGsmConference.setActive();%0A                    break;%0A                case Connection.STATE_HOLDING:%0A                    mGsmConference.setOnHold();%0A                    break;%0A            %7D
 %0A        %7D%0A    %7D

Update GsmConference state to the same as its child calls.