/*
 * Copyright 2014 Jacopo Aliprandi, Dario Archetti
 * Copyright 2015 Stefano Cappa
 *
 * This file is part of SPF.
 *
 * SPF is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * SPF 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 Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with SPF.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package it.polimi.spf.wfd;

import android.content.Context;
import android.content.Intent;
import android.net.wifi.WpsInfo;
import android.net.wifi.p2p.WifiP2pConfig;
import android.net.wifi.p2p.WifiP2pDevice;
import android.net.wifi.p2p.WifiP2pGroup;
import android.net.wifi.p2p.WifiP2pInfo;
import android.net.wifi.p2p.WifiP2pManager;
import android.net.wifi.p2p.WifiP2pManager.Channel;
import android.net.wifi.p2p.nsd.WifiP2pDnsSdServiceInfo;
import android.net.wifi.p2p.nsd.WifiP2pDnsSdServiceRequest;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import android.widget.Toast;

import com.squareup.otto.Subscribe;

import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import it.polimi.spf.wfd.events.EternalConnectEvent;
import it.polimi.spf.wfd.events.Event;
import it.polimi.spf.wfd.events.HandlerSendBroadcastEvent;
import it.polimi.spf.wfd.events.MidConnectionEvent;
import it.polimi.spf.wfd.events.NineBus;
import it.polimi.spf.wfd.events.goEvent.GOConnectActionEvent;
import it.polimi.spf.wfd.events.goEvent.GOConnectionEvent;
import it.polimi.spf.wfd.exceptions.GroupException;
import it.polimi.spf.wfd.groups.GroupActor;
import it.polimi.spf.wfd.groups.GroupClientActor;
import it.polimi.spf.wfd.groups.GroupOwnerActor;
import it.polimi.spf.wfd.listeners.CustomDnsSdTxtRecordListener;
import it.polimi.spf.wfd.listeners.CustomDnsServiceResponseListener;
import it.polimi.spf.wfd.listeners.CustomizableActionListener;
import it.polimi.spf.wfd.listeners.GroupActorListener;
import it.polimi.spf.wfd.listeners.WfdMiddlewareListener;
import it.polimi.spf.wfd.util.WfdLog;
import it.polimi.spf.wfdadapter.WFDMiddlewareAdapter;
import lombok.Getter;
import lombok.Setter;

/**
 * WifiDirectMiddleware class provides a coordination layer that handles the discovery of peer,
 * the creation of the P2P Group and mediates the communication to the upper layer of the system.
 * In particular, this last function is made with a series of adapters whose aim is to adapt the
 * Wi-Fi Direct middleware interface to the ones required by SPF.
 */
public class WifiDirectMiddleware implements WifiP2pManager.ConnectionInfoListener {
    private final static String TAG = WifiDirectMiddleware.class.getSimpleName();
    private static final int THIS_DEVICE_IS_GO = 15;

    //this constants are also into GroupInfoFragment...why?
    //because, in theory this SPFApp should be moved in an external application and
    //for this reason some costants and informations must be replicated
    private static final String SERVICE_NAME = "service_name";
    private static final String SERVICE_ADDRESS = "service_address";
    private static final String SERVICE_IDENTIFIER = "service_identifier";
    private static final String SERVICES_ACTION = "it.polimi.spf.groupinfo.services";
    public static final String SERVICES_ADD = SERVICES_ACTION + "_add";
    public static final String SERVICES_REMOVE = SERVICES_ACTION + "_remove";
    public static final String SERVICES_REMOVE_ALL = SERVICES_ACTION + "_remove_all";

    private static final String CLIENT_IDENTIFIER = "client_identifier";
    private static final String CLIENTS_ACTION = "it.polimi.spf.groupinfo.clients";
    public static final String CLIENTS_ADD = CLIENTS_ACTION + "_add";
    public static final String CLIENTS_REMOVE = CLIENTS_ACTION + "_removed";
    public static final String CLIENTS_REMOVE_ALL = CLIENTS_ACTION + "_remove_all";

    private final Context mContext;
    private final String myIdentifier;
    private final WfdMiddlewareListener mListener;
    private final WfdBroadcastReceiver mReceiver;
    private WifiP2pManager mManager;
    private Channel mChannel;
    private GroupActor mGroupActor;
    private HandlerThread handlerThread;
    @Getter
    private WfdHandler wfdHandler;

    private int mPort;
    @Getter
    private boolean connected = false;
    @Getter
    private boolean isGroupCreated = false;
    @Setter
    private boolean proximityKilledByUser = false;
    @Setter
    private int goIntent;
    @Getter
    private boolean isAutonomous;

    public WifiDirectMiddleware(Context context, int goIntentFromSPFApp, boolean isAutonomous, String identifier, WfdMiddlewareListener listener) {
        WfdLog.d(TAG, "WifiDirectMiddleware (isAutonomous= " + isAutonomous +
                ") with goIntentFromSPFApp: " + goIntentFromSPFApp);
        this.mContext = context;
        this.mReceiver = new WfdBroadcastReceiver();
        this.mListener = listener;
        this.myIdentifier = identifier;
        this.goIntent = goIntentFromSPFApp;
        this.isAutonomous = isAutonomous;
    }

    /**
     * Method called by {@link WFDMiddlewareAdapter#connect()}
     */
    public void init() {
        WfdLog.d(TAG, "Called init()");

        initAndStartHandler();

        try {
            mPort = this.requestAvailablePortFromOs();
        } catch (IOException e) {
            mListener.onError();
            return;
        }

        mManager = (WifiP2pManager) mContext.getSystemService(Context.WIFI_P2P_SERVICE);
        mChannel = mManager.initialize(mContext, Looper.getMainLooper(), new WifiP2pManager.ChannelListener() {
            @Override
            public void onChannelDisconnected() {
                WfdLog.d(TAG, "Channel disconnected");
            }
        });

        mReceiver.register(mContext);
        NineBus.get().register(this);
    }

    private void initAndStartHandler() {
        WfdLog.d(TAG, "Handler in WifiDirectMiddleware initialized and started");
        handlerThread = new HandlerThread("wfd_middleware_handler_thread");
        handlerThread.start();
        wfdHandler = new WfdHandler(handlerThread.getLooper());
    }

    /**
     * Method to connect. Called by {@link it.polimi.spf.wfdadapter.WFDMiddlewareAdapter#connect()}.
     */
    public void connect() {
        if (isConnected()) {
            Log.w(TAG, "Connect called but isConnected() == true");
            return;
        }

        this.startRegistration();
        this.discoverService();

        WfdLog.d(TAG, "-----> " + isAutonomous + " , " + goIntent);
        if (this.isAutonomous() && this.goIntent == 15) {
            this.createGroup();
        }

        this.connected = true;
    }

    /**
     * Registers a local service.
     */
    private void startRegistration() {
        //  Create a string map containing information about your service.
        Map<String, String> mRecordMap = new HashMap<>();
        mRecordMap.put(Configuration.PORT, Integer.toString(mPort));
        mRecordMap.put(Configuration.IDENTIFIER, myIdentifier);

        // Service information.  Pass it an instance name, service type
        // Configuration.SERVICE_REG_TYPE , and the map containing
        // information other devices will want once they connect to this one.
        WifiP2pDnsSdServiceInfo mInfo = WifiP2pDnsSdServiceInfo.newInstance(
                Configuration.SERVICE_INSTANCE + myIdentifier,
                Configuration.SERVICE_REG_TYPE, mRecordMap);

        // Add the local service, sending the service info, network channel,
        // and listener that will be used to indicate success or failure of
        // the request.
        mManager.addLocalService(mChannel, mInfo,
                new CustomizableActionListener(
                        this.mContext,
                        TAG,
                        "Added Local Service",
                        null,
                        "Failed to add a service",
                        "Failed to add a service",
                        true)); //important: sets true to get detailed message when this method fails

    }

    /**
     * Method to discover services and put the results
     * in {@link it.polimi.spf.wfd.ServiceList}.
     * This method updates also the discovery menu item.
     */
    private void discoverService() {
        /*
         * Register listeners for DNS-SD services. These are callbacks invoked
         * by the system when a service is actually discovered.
         */
        mManager.setDnsSdResponseListeners(mChannel,
                new CustomDnsServiceResponseListener(this), new CustomDnsSdTxtRecordListener());

        // After attaching listeners, create a service request and initiate
        // discovery.
        WifiP2pDnsSdServiceRequest mServiceRequest = WifiP2pDnsSdServiceRequest.newInstance();

        //initiates discovery
        mManager.addServiceRequest(mChannel, mServiceRequest,
                new CustomizableActionListener(
                        this.mContext,
                        TAG,
                        "Added service discovery request",
                        null,
                        "Failed adding service discovery request",
                        "Failed adding service discovery request",
                        true)); //important: sets true to get detailed message when this method fails

        //starts services discovery
        mManager.discoverServices(mChannel,
                new CustomizableActionListener(
                        this.mContext,
                        TAG,
                        "Service discovery initiated",
                        null,
                        "Failed starting service discovery",
                        "Failed starting service discovery",
                        true)); //important: sets true to get detailed message when this method fails
    }

    public void disconnect() {
        if (!isConnected()) {
            Log.e(TAG, "disconnect called but isConnected() == false");
            return;
        }

        if (handlerThread != null) {
            handlerThread.quit();
        }
        handlerThread = null;
        wfdHandler = null;

        mReceiver.unregister(mContext);
        NineBus.get().unregister(this);

        mManager.clearServiceRequests(mChannel, new CustomizableActionListener(
                this.mContext,
                TAG,
                "ClearServiceRequests success",
                null,
                "ClearServiceRequests failure",
                null,
                false)); //important: sets false if you don't want detailed messages when this method fails

        mManager.clearLocalServices(mChannel, new CustomizableActionListener(
                this.mContext,
                TAG,
                "ClearLocalServices success",
                null,
                "ClearLocalServices failure",
                null,
                false)); //important: sets false if you don't want detailed messages when this method fails

        mManager.cancelConnect(mChannel, new CustomizableActionListener(
                this.mContext,
                TAG,
                "CancelConnect success",
                null,
                "CancelConnect failure",
                null,
                false)); //important: sets false if you don't want detailed messages when this method fails

        mManager.removeGroup(mChannel, new WifiP2pManager.ActionListener() {
            @Override
            public void onSuccess() {
                WfdLog.d(TAG, "RemoveGroup success");
            }

            @Override
            public void onFailure(int reason) {
                WfdLog.e(TAG, "RemoveGroup failure, with reason: " + reason);
            }
        });

        this.connected = false;

        if (mGroupActor != null) {
            this.postEvent(new GOConnectActionEvent(GOConnectActionEvent.DISCONNECT_STRING));
            mGroupActor = null;
        }

        notifyServiceToGui(SERVICES_REMOVE_ALL, null);
        notifyConnectedDeviceToGui(CLIENTS_REMOVE_ALL, null);

        ServiceList.getInstance().getServiceList().clear();
    }

    private void becomeStandardGroupOwner() {
        List<WiFiP2pService> validClients = ServiceList.getInstance().getValidClients(myIdentifier);
        if (validClients == null || validClients.size() == 0) {
            WfdLog.d(TAG, "no device address eligible for connection");
            return;
        }

        //TODO TODO FIXME FIXME FIXME in this first impl i choose the first element in the list
        String deviceAddress = validClients.get(0).getPeerAddress();
        this.establishStandardConnection(validClients, deviceAddress);
    }

    private void becomeAutonomousGroupOwner() {
        mManager.createGroup(mChannel, new WifiP2pManager.ActionListener() {
            @Override
            public void onSuccess() {
                WfdLog.d(TAG, "CreateGroup success");
            }

            @Override
            public void onFailure(int reason) {
                WfdLog.e(TAG, "becomeAutonomousGroupOwner() - CreateGroup failure");
                boolean eternalActivated = EternalConnect.get().onGroupCreationFailed(proximityKilledByUser);
                if (!eternalActivated) {
                    Toast.makeText(mContext, "CreateGroup (autonomous) failure!", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

    public void createGroup() {
        WfdLog.d(TAG, "createGroup()");
        if (isGroupCreated || !this.connected) {
            WfdLog.d(TAG, "group already created or middleware not started");
            return;
        }
        WfdLog.d(TAG, "attempt to create group");

        isGroupCreated = true;

        if (goIntent == THIS_DEVICE_IS_GO) {
            WfdLog.d(TAG, "This device want to be an autonomous GO, because has gointent: " + goIntent);

            if (isAutonomous) {
                this.becomeAutonomousGroupOwner();
            } else {
                this.becomeStandardGroupOwner();
            }

        } else {
            WfdLog.d(TAG, "This device want to be a client, because has gointent: " + goIntent);

            List<WiFiP2pService> validGroupOwners = ServiceList.getInstance().getValidGroupOwners(myIdentifier);
            if (validGroupOwners == null || validGroupOwners.size() == 0) {
                WfdLog.d(TAG, "no device address eligible for connection");
                return;
            }

            //TODO TODO FIXME FIXME FIXME in this first impl i choose the first element in the list
            //TODO but i should check between all the GO in proximity and I should choose the correct one.
            String deviceAddress = validGroupOwners.get(0).getPeerAddress();
            this.establishStandardConnection(validGroupOwners, deviceAddress);
        }
    }

    private void establishStandardConnection(List<WiFiP2pService> servicesList, String destinationAddress) {
        WfdLog.d(TAG, "connect target device found, " +
                "device address: " + destinationAddress + " device name: " + servicesList.get(0).getInstanceName() +
                " device id: " + servicesList.get(0).getIdentifier() + ", and goIntent: " + goIntent);

        WifiP2pConfig config = new WifiP2pConfig();
        config.deviceAddress = destinationAddress;
        config.wps.setup = WpsInfo.PBC;
        config.groupOwnerIntent = this.goIntent;

        mManager.connect(mChannel, config, new WifiP2pManager.ActionListener() {
            @Override
            public void onSuccess() {
                WfdLog.d(TAG, "Connect success");
            }

            @Override
            public void onFailure(int reason) {
                WfdLog.e(TAG, "Connect failure");
                boolean eternalActivated = EternalConnect.get().onConnectFailed(proximityKilledByUser);
                if (!eternalActivated) {
                    Toast.makeText(mContext, "Connect failure!", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

    @Override
    public void onConnectionInfoAvailable(final WifiP2pInfo info) {
        if (mGroupActor != null) {
            Log.e(TAG, "Error onConnectionInfoAvailable, returning...");
            return;
        }

        if (info == null) {
            Log.e(TAG, "Error onConnectionInfoAvailable, info==null, returning...");
            return;
        }

        WfdLog.d(TAG, "connection info available, with info.isGroupOwner " + info.isGroupOwner);

        //here the connection info is available, but groups aren't created yet.
        //Groups are really available only in GroupOwnerInfoListener and GroupClientInfoListener
        //after the mManager.requestGroupInfo
        if (info.isGroupOwner) {
            mManager.requestGroupInfo(mChannel, new GroupOwnerInfoListener());
        } else {
            mManager.requestGroupInfo(mChannel, new GroupClientInfoListener(info));
        }
    }

    private void instantiateGroupClient(InetAddress groupOwnerAddress, int destPort) {
        WfdLog.d(TAG, "Instantiating group client's logic");
        mGroupActor = new GroupClientActor(groupOwnerAddress, destPort, actorListener, myIdentifier);
        this.postEvent(new GOConnectActionEvent(GOConnectActionEvent.CONNECT_STRING));

        EternalConnect.get().eternalCompletedSuccessfully();
    }

    private void instantiateGroupOwner() throws IOException {
        WfdLog.d(TAG, "Instantiating group owner's logic");
        mGroupActor = new GroupOwnerActor(mPort, actorListener, myIdentifier);
        this.postEvent(new GOConnectActionEvent(GOConnectActionEvent.CONNECT_STRING));

        EternalConnect.get().eternalCompletedSuccessfully();
    }

    public void onNetworkConnected() {
        WfdLog.d(TAG, "onNetworkConnected(): requesting connection info");
        if (mManager != null) {
            mManager.requestConnectionInfo(mChannel, this);
        }
    }

    public void onNetworkDisconnected() {
        WfdLog.d(TAG, "onNetworkDisconnected");
        if (mGroupActor != null) {
            WfdLog.d(TAG, "Disconnecting");
            this.postEvent(new GOConnectActionEvent(GOConnectActionEvent.DISCONNECT_STRING));
        }
        isGroupCreated = false;
        mGroupActor = null;
        if (mManager != null) {
            mManager.removeGroup(mChannel, null);
        }

        notifyServiceToGui(SERVICES_REMOVE_ALL, null);
        notifyConnectedDeviceToGui(CLIENTS_REMOVE_ALL, null);

        EternalConnect.get().onNetworkDisconnected(proximityKilledByUser, isAutonomous);
    }

    public void sendMessage(WfdMessage msg, String targetId) throws IOException {
        msg.setSenderId(myIdentifier);
        msg.setReceiverId(targetId);
        if (mGroupActor == null) {
            throw new IOException("Group not yet instantiated");
        }
        mGroupActor.sendMessage(msg);

    }

    public void sendMessageBroadcast(WfdMessage msg) throws IOException, GroupException {
        msg.setSenderId(myIdentifier);
        msg.setReceiverId(WfdMessage.BROADCAST_RECEIVER_ID);
        if (mGroupActor == null) {
            throw new GroupException(GroupException.Reason.NOT_INSTANTIATED_YET, "Group not yet instantiated");
        }
        mGroupActor.sendMessage(msg);
    }

    public WfdMessage sendRequestMessage(WfdMessage msg, String targetId) {
        msg.setSenderId(myIdentifier);
        msg.setReceiverId(targetId);
        msg.setType(WfdMessage.TYPE_REQUEST);
        try {
            if (mGroupActor == null) {
                return null;
            }
            return mGroupActor.sendRequestMessage(msg);
        } catch (IOException e) {
            WfdLog.d(TAG, "sendRequestMessage( ) error", e);
            return null;
        }
    }

    public void showConnectedMessage() {
        //handler, because i must execute this code on the ui thread
        new Handler(Looper.getMainLooper()).post(new Runnable() {
            @Override
            public void run() {
                if (goIntent != 15) {
                    Toast.makeText(mContext, "Connected to GO", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

    private int requestAvailablePortFromOs() throws IOException {
        ServerSocket mServerSocket = new ServerSocket(0);
        int assignedPort = mServerSocket.getLocalPort();

        //close this socket, but i want to maintain in a global variable the assigned port
        try {
            mServerSocket.close();
        } catch (IOException e) {
            WfdLog.d(TAG, "IOException during mServerSocket.close().");
        }

        return assignedPort;
    }

    private void postEvent(Event event) {
        NineBus.get().post(event);
    }

    /**
     * Method to send in broadcast a new discovered service to the UI.
     */
    public void notifyServiceToGui(@NonNull String notificationType, @Nullable WiFiP2pService service) {
        Intent intent = new Intent();
        if (service != null) {
            intent.putExtra(SERVICE_NAME, service.getDevice().deviceName);
            intent.putExtra(SERVICE_ADDRESS, service.getDevice().deviceAddress);
            intent.putExtra(SERVICE_IDENTIFIER, service.getIdentifier());
        }
        intent.setAction(notificationType);
        mContext.sendBroadcast(intent);
    }

    /**
     * Method to send in broadcast a new connected device to the UI
     */
    public void notifyConnectedDeviceToGui(@NonNull String notificationType, @Nullable String id) {
        Intent intent = new Intent();
        if (id != null) {
            intent.putExtra(CLIENT_IDENTIFIER, id);
        }
        intent.setAction(notificationType);
        mContext.sendBroadcast(intent);
    }

    @Subscribe
    public void onMidConnectionEvent(MidConnectionEvent e) {
        switch (e.getType()) {
            case WfdBroadcastReceiver.NETWORK_CONNECTED:
                this.onNetworkConnected();
                break;
            case WfdBroadcastReceiver.NETWORK_DISCONNECTED:
                this.onNetworkDisconnected();
                break;
            case WfdBroadcastReceiver.P2P_ENABLED:
                Log.d(TAG, "Wi-Fi Direct enabled");
                break;
            case WfdBroadcastReceiver.P2P_DISABLED:
                Toast.makeText(mContext, "Wi-Fi Direct not enabled!", Toast.LENGTH_SHORT).show();
                break;
            case WfdBroadcastReceiver.PEERS_CHANGED:
                //TODO if necessary
                break;
            default:
                WfdLog.d(TAG, "Unknown onMidConnectionEvent");
        }
    }

    @Subscribe
    public void onClientConnectedToGO(GOConnectionEvent e) {
        switch (e.getType()) {
            case GOConnectionEvent.CONNECTED:
                if (mContext != null) {
                    Toast.makeText(mContext, "Client connected", Toast.LENGTH_SHORT).show();
                }
                //not necessary to call notifyConnectedDeviceToGui, because also called from
                //the WFDMiddlewareAdapter when the connection is fully established.
                break;
            case GOConnectionEvent.DISCONNECTED:
                if (mContext != null) {
                    Toast.makeText(mContext, "Client disconnected", Toast.LENGTH_SHORT).show();
                }
                notifyConnectedDeviceToGui(CLIENTS_REMOVE, e.getId());
                break;
            default:
                WfdLog.d(TAG, "Error, unknown GOConnectionEvent's state");
        }
    }

    /**
     * subscribe to catch events posted by {@link EternalConnect}.
     */
    @Subscribe
    public void onEternalConnectUpdate(EternalConnectEvent e) {
        if (e == null || e.getType() == null) {
            return;
        }

        WfdLog.d(TAG, "onEternalConnectUpdate received event: " + e.getType());

        //if you want, in the future, you can change the behaviour of either a
        //simple eternal connect reconnection (autonomous go) or a cycle of the eternal connect (clients)
        switch (e.getType()) {
            case EternalConnectEvent.NEW_EC_CYCLE:
            case EternalConnectEvent.SIMPLE_EC_RECONNECTION:
                disconnect();
                init();
                connect();
                break;
        }
    }

    /**
     * subscribe to catch events posted by {@link WfdHandler}.
     */
    @Subscribe
    public void onSendBroadcastMessage(HandlerSendBroadcastEvent e) {
        try {
            this.sendMessageBroadcast(e.getMessage());
        } catch (GroupException e1) {
            WfdLog.e(TAG, "GroupException " + e.getMessage());
        } catch (IOException e1) {
            WfdLog.e(TAG, "handleMessage IOException", e1);
        }
    }

    private final GroupActorListener actorListener = new GroupActorListener() {

        @Override
        public void onMessageReceived(WfdMessage msg) {
            WfdLog.d(TAG, "GroupActorListener.onMessageReceived - msg= " + msg);
            mListener.onMessageReceived(msg);
        }

        @Override
        public void onError() {
            WfdLog.e(TAG, "GroupActorListener.onError");
            if (mGroupActor != null) {
                postEvent(new GOConnectActionEvent(GOConnectActionEvent.DISCONNECT_STRING));
            }
            isGroupCreated = false;
            mGroupActor = null;
            mManager.removeGroup(mChannel, null);
            mManager.requestConnectionInfo(mChannel, WifiDirectMiddleware.this);
        }

        @Override
        public WfdMessage onRequestMessageReceived(WfdMessage msg) {
            WfdLog.d(TAG, "GroupActorListener.onRequestMessageReceived - msg= " + msg);
            return mListener.onRequestMessageReceived(msg);
        }

        @Override
        public void onInstanceFound(String identifier) {
            WfdLog.d(TAG, "GroupActorListener.onInstanceFound - identifier= " + identifier);
            mListener.onInstanceFound(identifier);
        }

        @Override
        public void onInstanceLost(String identifier) {
            Log.e(TAG, "GroupActorListener.onInstanceLost - identifier= " + identifier);
            mListener.onInstanceLost(identifier);
        }
    };

    public class GroupClientInfoListener implements WifiP2pManager.GroupInfoListener {
        private WifiP2pInfo info;

        public GroupClientInfoListener(WifiP2pInfo info) {
            this.info = info;
        }

        @Override
        public void onGroupInfoAvailable(WifiP2pGroup group) {
            if (group == null) {
                // happens when the go goes away and the
                // framework does not have time to update the
                // connection loss
                return;
            }
            WifiP2pDevice groupOwnerDevice = group.getOwner();

            WfdLog.d(TAG, "requestGroupInfo - groupOwnerDeviceaddress: " + groupOwnerDevice.deviceAddress);

            WiFiP2pService service = ServiceList.getInstance().getServiceByDevice(groupOwnerDevice);

            WfdLog.d(TAG, "requestGroupInfo - service: " + service);

            if (service == null) {
                WfdLog.e(TAG, "service is null");
                mManager.removeGroup(mChannel, null);
            } else {
                int destPort = service.getPort();
                instantiateGroupClient(info.groupOwnerAddress, destPort);
            }
        }
    }

    public class GroupOwnerInfoListener implements WifiP2pManager.GroupInfoListener {
        @Override
        public void onGroupInfoAvailable(WifiP2pGroup group) {
            try {
                instantiateGroupOwner();
            } catch (IOException e) {
                WfdLog.e(TAG, "Impossible to instantiate the GroupOwner Actor!", e);
            }
        }
    }
}


/*
 * Copyright 2014 Jacopo Aliprandi, Dario Archetti
 * Copyright 2015 Stefano Cappa
 *
 * This file is part of SPF.
 *
 * SPF is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * SPF 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 Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with SPF.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package it.polimi.spf.lib;

import it.polimi.spf.lib.profile.SPFRemoteProfile.RemoteProfile;
import it.polimi.spf.lib.profile.SPFRemoteProfile;
import it.polimi.spf.lib.services.SPFServiceExecutor;
import it.polimi.spf.shared.model.BaseInfo;
import it.polimi.spf.shared.model.SPFActivity;

/**
 * Instances of the {@code Person} class represent people found in the proximity
 * of the device with whom the interaction is possible.
 *
 */
public class SPFPerson {

	public static final String SELF_IDENTIFIER = "#SELF";
	private String mIdentifier;
	private BaseInfo mBaseInfo;

	// TODO @hide
	public SPFPerson(String uniqueIdentifier) {
		this(uniqueIdentifier, null);
	}

	// TODO @hide
	public SPFPerson(String uniqueIdentifier, BaseInfo baseInfo) {
		this.mIdentifier = uniqueIdentifier != null ? uniqueIdentifier : SELF_IDENTIFIER;
		this.mBaseInfo = baseInfo;
	}

	/**
	 * Returns the identifier of the person. If the person represents the user
	 * himself, the methods returns the value contained in the constant
	 * SELF_IDENTIFIER.
	 *
	 * @return the identifier of the person
	 */
	public String getIdentifier() {
		return mIdentifier;
	}

	/**
	 * Returns the basic information of the person. If it is unavailable, an
	 * empty string is retrieved.
	 *
	 * @return the basic information about the person
	 */
	public BaseInfo getBaseInfo() {
		return mBaseInfo;
	}

	/**
	 * @return true if the person is self
	 */
	public boolean isSelf() {
		return mIdentifier.equals(SELF_IDENTIFIER);
	}

	@Override
	public String toString() {
		return getIdentifier();
	}

	/**
	 * Shorthand method to retrieve the RemoteProfile of {@code this} person.
	 *
	 * @param spf
	 * @return
	 */
	public RemoteProfile getProfile(SPF spf) {
		SPFRemoteProfile rp = (SPFRemoteProfile) spf.getComponent(SPF.REMOTE_PROFILE);
		return rp.getProfileOf(this);
	}

	/**
	 * Shorthand method to create an invocation interface for {@code this} person.
	 *
	 * @param serviceInterface
	 * @param spf
	 * @return
	 */
	public <E> E getServiceInterface(Class<E> serviceInterface, SPF spf) {
		if (spf == null || serviceInterface == null) {
			throw new NullPointerException();
		}

		return spf
				.<SPFServiceExecutor> getComponent(SPF.SERVICE_EXECUTION)
				.createStub(this, serviceInterface, getClass().getClassLoader());
	}

	public boolean sendActivity(SPF spf, SPFActivity activity) {
		SPFServiceExecutor executor = spf.getComponent(SPF.SERVICE_EXECUTION);
		return executor.sendActivity(mIdentifier, activity);
	}

	@Override
	public boolean equals(Object o) {
		if (o == null || !(o instanceof SPFPerson)) {
			return false;
		}

		return ((SPFPerson) o).mIdentifier.equals(mIdentifier);
	}

	@Override
	public int hashCode() {
		return mIdentifier.hashCode();
	}
}


/**
 * The MIT License
 *
 * Copyright (C) 2015 Asterios Raptis
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package de.alpharogroup.xml.tag;

import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import de.alpharogroup.clone.object.CloneObjectExtensions;
import de.alpharogroup.collections.list.ListFactory;
import de.alpharogroup.collections.map.MapFactory;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.FieldDefaults;

/**
 * The class {@link Tag} represents an tag for xml or html where you can set the position of the
 * child tags
 */
@Getter
@Setter
@ToString(exclude = { "attributes", "childTagPositions" })
@EqualsAndHashCode(exclude = { "attributes", "childTagPositions" })
@NoArgsConstructor
@AllArgsConstructor
@Builder(toBuilder = true)
@FieldDefaults(level = AccessLevel.PRIVATE)
public class Tag implements Serializable
{

	/**
	 * The Constant serialVersionUID.
	 */
	private static final long serialVersionUID = 1L;

	/** The attributes of the tag. */
	private Map<String, String> attributes;

	/** The children. */
	private List<ChildTagPosition> childTagPositions;

	/** The content of the tag. */
	private String content;

	/** The flag endTag signals if this tag has an ending tag. */
	private boolean endTag;

	/** The name of the tag. */
	private String name;

	/**
	 * Adds the attribute with the given name and value.
	 *
	 * @param name
	 *            the name
	 * @param value
	 *            the value
	 * @return the string
	 */
	public String addAttribute(final String name, final String value)
	{
		if (getAttributes() == null)
		{
			this.attributes = MapFactory.newLinkedHashMap();
		}
		return getAttributes().put(name, value);
	}

	/**
	 * Adds the given {@link Tag} child to the given position.
	 *
	 * @param child
	 *            the {@link Tag} child
	 * @param position
	 *            the position to add
	 * @return true, if the given {@link Tag} child was added successfully to the given position
	 *         otherwise false
	 */
	public boolean addChild(final Tag child, final Integer position)
	{
		if (getChildren() == null)
		{
			this.childTagPositions = ListFactory.newArrayList();
		}
		final ChildTagPosition childTagPosition = ChildTagPosition.builder().child(child)
			.position(position).build();
		return getChildren().add(childTagPosition);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Object clone() throws CloneNotSupportedException
	{
		try
		{
			return CloneObjectExtensions.clone(this);
		}
		catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException
			| InvocationTargetException | ClassNotFoundException | InstantiationException
			| IOException e)
		{
			throw new CloneNotSupportedException();
		}
	}

	/**
	 * Gets the children.
	 *
	 * @return the children
	 */
	public List<ChildTagPosition> getChildren()
	{
		return this.childTagPositions;
	}

	/**
	 * Removes the attribute with the given name.
	 *
	 * @param name
	 *            the name
	 * @return the string
	 */
	public String removeAttribute(final String name)
	{
		if (getAttributes() != null)
		{
			getAttributes().remove(name);
		}
		return null;
	}

	/**
	 * Removes the given {@link Tag} child
	 *
	 * @param child
	 *            the {@link Tag} child
	 * @return true, if the given {@link Tag} child was removed successfully otherwise false
	 */
	public boolean removeChild(final Tag child)
	{
		if (getChildren() != null)
		{
			ChildTagPosition found = null;
			for (final ChildTagPosition childTagPosition : getChildren())
			{
				if (childTagPosition.getChild().equals(child))
				{
					found = childTagPosition;
					break;
				}
			}
			if (found != null)
			{
				return getChildren().remove(found);
			}
		}
		return false;
	}

	/**
	 * Creates from this {@link Tag} object an xml string.
	 *
	 * @return the string buffer
	 */
	public String toXmlString()
	{
		final StringBuilder buffer = new StringBuilder();
		buffer.append("<");
		buffer.append(getName());
		Optional<String> attr = TagExtensions.attributesToString(getAttributes());
		if (attr.isPresent())
		{
			buffer.append(attr.get());
		}
		if (isEndTag())
		{
			buffer.append(">");
			if (getChildren() != null && !getChildren().isEmpty())
			{
				String processingContent = getContent();
				Integer lastPosition = 0;
				for (final ChildTagPosition child : getChildren())
				{
					final String subContent = getContent().substring(lastPosition,
						child.getPosition());
					lastPosition = child.getPosition();
					processingContent = processingContent.substring(lastPosition,
						processingContent.length());
					buffer.append(subContent);
					buffer.append(child.getChild().toXmlString());
				}
				buffer.append(processingContent);
			}
			else
			{
				buffer.append(getContent());
			}
			buffer.append("</");
			buffer.append(getName());
			buffer.append(">");
		}
		else
		{
			buffer.append("/>");
		}
		return buffer.toString();
	}

}


/**
 * The MIT License
 *
 * Copyright (C) 2015 Asterios Raptis
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package de.alpharogroup.xml.resourcebundle;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;

/**
 * The class XmlResourceBundleControl.
 */
public class XmlResourceBundleControl extends ResourceBundle.Control
{

	/** The Constant XML. */
	private static final String XML = "xml";

	/**
	 * {@inheritDoc}
	 */
	@Override
	public List<String> getFormats(final String baseName)
	{
		if (baseName == null)
		{
			throw new NullPointerException("baseName is null");
		}
		return Collections.unmodifiableList(Arrays.asList(XML));
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public ResourceBundle newBundle(final String baseName, final Locale locale, final String format,
		final ClassLoader loader, final boolean reload)
		throws IllegalAccessException, InstantiationException, IOException
	{

		if (baseName == null || locale == null || format == null || loader == null)
		{
			throw new NullPointerException();
		}
		ResourceBundle bundle = null;
		if (!format.equals(XML))
		{
			return null;
		}

		final String bundleName = toBundleName(baseName, locale);
		final String resourceName = toResourceName(bundleName, format);
		final URL url = loader.getResource(resourceName);
		if (url == null)
		{
			return null;
		}
		final URLConnection connection = url.openConnection();
		if (connection == null)
		{
			return null;
		}
		if (reload)
		{
			connection.setUseCaches(false);
		}
		final InputStream stream = connection.getInputStream();
		if (stream == null)
		{
			return null;
		}
		final BufferedInputStream bis = new BufferedInputStream(stream);
		bundle = new XmlResourceBundle(bis);

		bis.close();

		return bundle;
	}

}


/*
 * Copyright 2014 Jacopo Aliprandi, Dario Archetti
 *
 * This file is part of SPF.
 *
 * SPF is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * SPF 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 Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with SPF.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package it.polimi.spf.lib.services;

import it.polimi.spf.lib.Component;
import it.polimi.spf.lib.SPFPerson;

import it.polimi.spf.shared.aidl.LocalServiceManager;
import it.polimi.spf.shared.model.InvocationRequest;
import it.polimi.spf.shared.model.InvocationResponse;
import it.polimi.spf.shared.model.SPFActivity;
import it.polimi.spf.shared.model.SPFError;
import it.polimi.spf.shared.model.SPFServiceDescriptor;

import android.content.Context;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;

/**
 * SPF component to create invocation stubs to invoke remote methods.
 *
 * @author darioarchetti
 *
 */
public final class SPFServiceLocalExecutor extends Component<SPFServiceLocalExecutor, LocalServiceManager> {

	private static final String SERVICE_INTENT = "it.polimi.spf.services.LocalServiceExecutor";
	private static final Component.Descriptor<SPFServiceLocalExecutor, LocalServiceManager> DESCRIPTOR = new Component.Descriptor<SPFServiceLocalExecutor, LocalServiceManager>() {

		@Override
		public String getActionName() {
			return SERVICE_INTENT;
		}

		@Override
		public LocalServiceManager castInterface(IBinder binder) {
			return LocalServiceManager.Stub.asInterface(binder);
		}

		@Override
		public SPFServiceLocalExecutor createInstance(Context context, LocalServiceManager serviceInterface, ServiceConnection connection, ConnectionCallback<SPFServiceLocalExecutor> callback) {
			return new SPFServiceLocalExecutor(context, serviceInterface, connection, callback);
		}
	};

	public static void load(Context context, final Callback callback) {
		Component.load(context, DESCRIPTOR, callback);
	}

	protected SPFServiceLocalExecutor(Context context, LocalServiceManager serviceInterface, ServiceConnection connection, ConnectionCallback<SPFServiceLocalExecutor> callback) {
		super(context, serviceInterface, connection, callback);
	}

	private InvocationStub.Target mLocalInvocationTarget = new InvocationStub.Target() {
		@Override
		public void prepareArguments(Object[] arguments) throws ServiceInvocationException {
			String token = getAccessToken();
			SPFError error = new SPFError();
			try {
				for (Object param : arguments) {
					if (param instanceof SPFActivity) {
						getService().injectInformationIntoActivity(token, (SPFActivity) param, error);
						if (!error.isOk()) {
							handleError(error);
							throw new ServiceInvocationException(error.toString());
						}
					}
				}
			} catch (RemoteException e) {
				catchRemoteException(e);
				throw new ServiceInvocationException(e.getClass().getSimpleName());
			}
		}

		@Override
		public InvocationResponse executeService(InvocationRequest request) throws ServiceInvocationException {
			String token = getAccessToken();
			SPFError error = new SPFError();

			try {
				InvocationResponse response = getService().executeLocalService(token, request, error);

				if (!error.isOk()) {
					handleError(error);
					response = InvocationResponse.error(error.toString());
				}

				return response;
			} catch (RemoteException e) {
				catchRemoteException(e);
				throw new ServiceInvocationException(e.getClass().getSimpleName());
			}

		}
	};

	/**
	 * Creates an invocation stub to send service invocation requests to the
	 * local person performing method calls. The stub is created from a provided
	 * service interface, which must be annotated with {@link ServiceInterface}
	 * describing the service. The method returns an object implementing the
	 * aforementioned interface that can be used to perform method invocation.
	 *
	 * @param serviceInterface
	 *            - the interface of the service.
	 * @param classLoader
	 *            - the ClassLoader to load classes.
	 * @return an invocation stub to perform method calls.
	 */
	public <E> E createStub(Class<E> serviceInterface, ClassLoader classLoader) {
		return InvocationStub.from(serviceInterface, classLoader, mLocalInvocationTarget);
	}

	/**
	 * Creates an invocation stub to send service invocation requests to a
	 * target {@link SPFPerson} providing the name and the parameter list. The
	 * object is created from a {@link ServiceDescriptor} containing the
	 * required details.
	 *
	 * @param target
	 *            - the person who the service invocation requests will be
	 *            dispatched to.
	 * @param descriptor
	 *            - the {@link ServiceDescriptor} of the service whose methods
	 *            to invoke.
	 * @return a {@link ServiceInvocationStub} to perform invocations of remote
	 *         services.
	 */
	public InvocationStub createStub(SPFServiceDescriptor descriptor) {
		return InvocationStub.from(descriptor, mLocalInvocationTarget);
	}

	/**
	 * Dispatches an activity to the local SPF instance. The framework will
	 * perform information injection into the activity: such information will be
	 * available to the caller once the method ends.
	 *
	 * @param activity
	 *            - the activity to dispatch.
	 * @return - true if the activity has been correctly consumed.
	 */
	public boolean sendActivityLocally(SPFActivity activity) {
		String token = getAccessToken();
		SPFError err = new SPFError();
		InvocationResponse resp;

		try {
			getService().injectInformationIntoActivity(token, activity, err);
			if (!err.isOk()) {
				handleError(err);
				return false;
			}

			resp = getService().sendActivityLocally(token, activity, err);
		} catch (RemoteException e) {
			// TODO Error Management
			return false;
		}

		if (!err.isOk()) {
			handleError(err);
			return false;
		}

		if (!resp.isResult()) {
			return false;
		}

		return GsonHelper.gson.fromJson(resp.getPayload(), Boolean.class);
	}

	// We do not let Callback implement BaseCallback, otherwise applications
	// would need to cast Component<> to the actual implementation.
	public interface Callback extends ConnectionCallback<SPFServiceLocalExecutor> {

	}

	private void catchRemoteException(RemoteException e) {
		disconnect();
		getCallback().onDisconnect();
	}
}


/*
 * Copyright 2014 Jacopo Aliprandi, Dario Archetti
 *
 * This file is part of SPF.
 *
 * SPF is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * SPF 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 Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with SPF.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package it.polimi.spf.lib.services;

import it.polimi.spf.shared.model.InvocationRequest;
import it.polimi.spf.shared.model.InvocationResponse;
import it.polimi.spf.shared.model.SPFServiceDescriptor;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import com.google.gson.JsonArray;
import com.google.gson.JsonParser;

/**
 * Wrapper class for services that eases the invocation of methods. A service is
 * made up of a ServiceInterface (an interface annotated with
 * {@link ServiceInterface}) that describes the service and its methods, and of
 * an implementation of such interface.
 */
public class ServiceWrapper {

	private static class ErrorMsg {
		public final static String METHOD_NOT_FOUND = "Method %s not found in service %s.";
		public final static String ILLEGAL_ARGUMENT = "Illegal argument provided for method invocation.";
	}

	private SPFServiceDescriptor mServiceDescriptor;
	private Object mImplementation;
	private Map<String, Method> mMethodIndex;

	/**
	 * Creates a new wrapper for the given service. The given Service Interface
	 * will be validated (see {@link ServiceValidator} for the constraints),
	 * illegal service interface will cause an unchecked
	 * IllegalServiceException.
	 *
	 * @param context
	 *            - the Android context
	 * @param serviceInterface
	 *            - the service interface
	 * @param implementation
	 *            - the service implementation
	 */
	public ServiceWrapper(Class<?> serviceInterface, Object implementation) {
		ServiceValidator.validateInterface(serviceInterface, ServiceValidator.TYPE_PUBLISHED);
		ServiceInterface service = serviceInterface.getAnnotation(ServiceInterface.class);

		this.mServiceDescriptor = ServiceInterface.Convert.toServiceDescriptor(service);
		this.mImplementation = implementation;
		this.mMethodIndex = new HashMap<String, Method>();

		for (Method m : serviceInterface.getMethods()) {
			if (m.isAnnotationPresent(ActivityConsumer.class)) {
				continue;
			}

			mMethodIndex.put(m.getName(), m);
		}
	}

	/**
	 * Invokes a method of the service.
	 *
	 * @param request
	 *            - The invocation request
	 * @return the return value
	 * @throws IllegalInvocationException
	 *             if the request does not match the service
	 * @throws InvocationTargetException
	 *             if an Exception is thrown during execution
	 */
	public InvocationResponse invokeMethod(InvocationRequest request) {
		String methodName = request.getMethodName();

		if (!mMethodIndex.containsKey(methodName)) {
			String msg = String.format(ErrorMsg.METHOD_NOT_FOUND, methodName, mServiceDescriptor.getServiceName());
			return InvocationResponse.error(msg);
		}

		Method m = mMethodIndex.get(methodName);
		Object[] params;
		try {
			params = deserializeParameters(request.getPayload(), m.getGenericParameterTypes());
		} catch (ServiceInvocationException e) {
			return InvocationResponse.error("Error deserializing parameters:" + e.getMessage());
		}

		try {
			Object result = m.invoke(mImplementation, params);
			String json = GsonHelper.gson.toJson(result);
			return InvocationResponse.result(json);
		} catch (IllegalAccessException e) {
			return InvocationResponse.error(e);
		} catch (IllegalArgumentException e) {
			return InvocationResponse.error(ErrorMsg.ILLEGAL_ARGUMENT);
		} catch (InvocationTargetException e) {
			return InvocationResponse.error(e.getCause());
		}
	}

	/**
	 * @return the name of the available methods.
	 */
	public Set<String> getAvailableMethods() {
		return mMethodIndex.keySet();
	}

	/**
	 * @return the {@link ServiceDescriptor} of this service.
	 */
	public SPFServiceDescriptor getServiceDescriptor() {
		return mServiceDescriptor;
	}

	public static <T> String readServiceName(Class<? super T> serviceInterface) {
		ServiceInterface service = serviceInterface.getAnnotation(ServiceInterface.class);
		if (service == null) {
			throw new IllegalArgumentException("Service interface does not have a ServiceInterface annotation.");
		}

		return service.name();
	}

	private Object[] deserializeParameters(String payload, Type[] parameterTypes) throws ServiceInvocationException {
		JsonParser parser = new JsonParser();
		JsonArray array = parser.parse(payload).getAsJsonArray();
		if (array.size() != parameterTypes.length) {
			throw new ServiceInvocationException("Parameter number mismatch");
		}

		Object[] params = new Object[parameterTypes.length];
		for (int i = 0; i < parameterTypes.length; i++) {
			params[i] = GsonHelper.gson.fromJson(array.get(i), parameterTypes[i]);
		}

		return params;
	}
}


/*
 * Copyright 2014 Jacopo Aliprandi, Dario Archetti
 * Copyright 2015 Stefano Cappa
 *
 * This file is part of SPF.
 *
 * SPF is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * SPF 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 Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with SPF.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package it.polimi.spf.app.view;

import it.polimi.spf.app.fragments.profile.Helper;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnMultiChoiceClickListener;
import android.util.AttributeSet;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.SpinnerAdapter;

/**
 * A Spinner view that does not dismiss the dialog displayed when the control is
 * "dropped down" and the user presses it. This allows for the selection of more
 * than one option.
 */
public class CircleSelectSpinner extends Spinner implements OnMultiChoiceClickListener {
	private String[] mItems = null;
	private boolean[] mSelection = null;
	private OnSelectionChangedListener mListener;
	private Helper mHelper;
	private ArrayAdapter<String> mProxyAdapter;

	public static interface OnSelectionChangedListener {
		void onItemAdded(String item);

		void onItemRemoved(String item);
	}

	/**
	 * Constructor for use when instantiating directly.
	 *
	 * @param context
	 */
	public CircleSelectSpinner(Context context) {
		super(context);

		mHelper = new Helper(context);
		mProxyAdapter = new ArrayAdapter<String>(context, android.R.layout.simple_spinner_item);
		super.setAdapter(mProxyAdapter);
	}

	/**
	 * Constructor used by the layout inflater.
	 *
	 * @param context
	 * @param attrs
	 */
	public CircleSelectSpinner(Context context, AttributeSet attrs) {
		super(context, attrs);

		mHelper = new Helper(context);
		mProxyAdapter = new ArrayAdapter<String>(context, android.R.layout.simple_spinner_item);
		super.setAdapter(mProxyAdapter);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void onClick(DialogInterface dialog, int which, boolean isChecked) {
		if (mSelection != null && which < mSelection.length) {
			mSelection[which] = isChecked;

			List<String> selected = getSelectedStrings();

			mProxyAdapter.clear();
			mProxyAdapter.add(mHelper.sumUpCircles(selected));
			setSelection(0);

			if (mListener != null) {
				String item = mItems[which];
				if (isChecked) {
					mListener.onItemAdded(item);
				} else {
					mListener.onItemRemoved(item);
				}
			}

		} else {
			throw new IllegalArgumentException("Argument 'which' is out of bounds.");
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@SuppressLint("ClickableViewAccessibility")
	@Override
	public boolean performClick() {
		AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
		builder.setMultiChoiceItems(mItems, mSelection, this);
		builder.show();
		return true;
	}

	/**
	 * MultiSelectSpinner does not support setting an adapter. This will throw
	 * an exception.
	 *
	 * @param adapter
	 */
	@Override
	public void setAdapter(SpinnerAdapter adapter) {
		throw new RuntimeException("setAdapter is not supported by MultiSelectSpinner.");
	}

	/**
	 * Sets the options for this spinner.
	 *
	 * @param items
	 */
	public void setItems(String[] items) {
		mItems = items;
		mSelection = new boolean[mItems.length];

		Arrays.fill(mSelection, false);
		refreshDisplayValue();
	}

	/**
	 * Sets the options for this spinner.
	 *
	 * @param items
	 */
	public void setItems(List<String> items) {
		mItems = items.toArray(new String[items.size()]);
		mSelection = new boolean[mItems.length];

		Arrays.fill(mSelection, false);
		refreshDisplayValue();
	}

	/**
	 * Sets the selected options based on an array of string.
	 *
	 * @param selection
	 */
	public void setSelection(String[] selection) {
		for (String sel : selection) {
			for (int j = 0; j < mItems.length; ++j) {
				if (mItems[j].equals(sel)) {
					mSelection[j] = true;
				}
			}
		}

		refreshDisplayValue();
	}

	/**
	 * Sets the selected options based on a list of string.
	 *
	 * @param selection
	 */
	public void setSelection(List<String> selection) {
		for (String sel : selection) {
			for (int j = 0; j < mItems.length; ++j) {
				if (mItems[j].equals(sel)) {
					mSelection[j] = true;
				}
			}
		}

		refreshDisplayValue();
	}

	/**
	 * Sets the selected options based on an array of positions.
	 *
	 * @param selectedIndicies
	 */
	public void setSelection(int[] selectedIndicies) {
		for (int index : selectedIndicies) {
			if (index >= 0 && index < mSelection.length) {
				mSelection[index] = true;
			} else {
				throw new IllegalArgumentException("Index " + index + " is out of bounds.");
			}
		}

		refreshDisplayValue();
	}

	/**
	 * Sets a listener to be notified when the selection changes
	 *
	 * @param listener
	 */
	public void setOnSelectionChangedListener(OnSelectionChangedListener listener) {
		this.mListener = listener;
	}

	/**
	 * Returns a list of strings, one for each selected item.
	 *
	 * @return
	 */
	public List<String> getSelectedStrings() {
		List<String> selection = new LinkedList<String>();
		for (int i = 0; i < mItems.length; ++i) {
			if (mSelection[i]) {
				selection.add(mItems[i]);
			}
		}
		return selection;
	}

	/**
	 * Returns a list of positions, one for each selected item.
	 *
	 * @return
	 */
	public List<Integer> getSelectedIndicies() {
		List<Integer> selection = new LinkedList<Integer>();
		for (int i = 0; i < mItems.length; ++i) {
			if (mSelection[i]) {
				selection.add(i);
			}
		}
		return selection;
	}

	private void refreshDisplayValue() {
		mProxyAdapter.clear();
		mProxyAdapter.add(mHelper.sumUpCircles(getSelectedStrings()));
		setSelection(0);
	}

}


/*
 * Copyright 2014 Jacopo Aliprandi, Dario Archetti
 * Copyright 2015 Stefano Cappa
 *
 * This file is part of SPF.
 *
 * SPF is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * SPF 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 Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with SPF.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package it.polimi.spf.app.fragments.profile;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;

import it.polimi.spf.app.R;
import it.polimi.spf.app.fragments.profile.ProfileFragment.Mode;
import it.polimi.spf.shared.model.ProfileField;

/**
 * Fragment that displays a list of {@link ProfileField} together with their
 * values. The {@link View}s used to display the values are obtained by the
 * parent fragment, intended to be {@link ProfileFragment}, with a call to
 * {@link ProfileFragment#createViewFor(ProfileField, ViewGroup)}. The
 * possibility of editing the values of fields depend on the {@link Mode} of the
 * parent.
 * <p/>
 * Instances of {@link ProfileFieldsFragment} can be created using {@link
 * ProfileFieldsFragment#new}
 *
 * @author darioarchetti
 */
public class ProfileFieldsFragment extends Fragment {

    /**
     * Key for the list of fields to show that should be put in the arguments
     * passed to this fragment.
     */
    private static final String EXTRA_FIELDS_TO_SHOW = "fields";

    /**
     * Creates a new instance of {@link ProfileFieldsFragment} to show the given
     * list of profile fields.
     *
     * @param fieldsToShow - the fields to show;
     * @return an instance of {@link ProfileFieldsFragment};
     */
    public static ProfileFieldsFragment newInstance(ProfileField<?>[] fieldsToShow) {
        if (fieldsToShow == null) {
            throw new NullPointerException();
        }

        Bundle b = new Bundle();
        b.putStringArray(EXTRA_FIELDS_TO_SHOW, ProfileField.toIdentifierList(fieldsToShow));
        ProfileFieldsFragment instance = new ProfileFieldsFragment();
        instance.setArguments(b);
        return instance;
    }

    private ProfileField<?>[] mFieldsToShow;
    private LinearLayout mViewContainer;
    private ProfileFragment mParent;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.profileedit_fragment, container, false);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        Bundle b = savedInstanceState == null ? getArguments() : savedInstanceState;

        if (b == null) {
            throw new IllegalArgumentException("No bundle to read data from");
        }

        mFieldsToShow = ProfileField.fromIdentifierList(b.getStringArray(EXTRA_FIELDS_TO_SHOW));
        mViewContainer = (LinearLayout) getView().findViewById(R.id.profileedit_field_container);
        mParent = (ProfileFragment) getParentFragment();
        onRefresh();
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putStringArray(EXTRA_FIELDS_TO_SHOW, ProfileField.toIdentifierList(mFieldsToShow));
    }

    /**
     * Refreshes the values of displayed fields.
     */
    public void onRefresh() {
        mViewContainer.removeAllViews();
        for (ProfileField<?> field : mFieldsToShow) {
            View child = mParent.createViewFor(field, mViewContainer);
            mViewContainer.addView(child);
        }
    }
}


/*
 * Copyright 2014 Jacopo Aliprandi, Dario Archetti
 * Copyright 2015 Stefano Cappa
 *
 * This file is part of SPF.
 *
 * SPF is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * SPF 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 Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with SPF.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package it.polimi.spf.framework.services;

import it.polimi.spf.shared.aidl.ClientExecutionService;
import it.polimi.spf.shared.model.InvocationRequest;
import it.polimi.spf.shared.model.SPFActivity;

import java.util.HashMap;
import java.util.Map;

import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

/**
 * Component that manages the communication with SPF services of enabled
 * applications to allow the dispatching of {@link InvocationRequest} and
 * {@link SPFActivity } with related responses. <br>
 * SPF Services are actually implemented with an Android {@link Service}
 * subclass available in the library that exposes the
 * {@link ClientExecutionService} binder. The communication thus is implemented
 * by binding to such service using
 * {@link Context#bindService(Intent, ServiceConnection, int)} and obtaining a
 * stub of such binder. The binder is obtained and referenced by an instance of
 * {@link AppServiceProxy}. After the creation, a proxy is kept in cache, ready
 * for future use; it may happen that the remote Service is terminated, so
 * before using a proxy from tha cache, the agent checks if the proxy is still
 * alive.
 *
 * @author darioarchetti
 *
 */
/* package */class AppCommunicationAgent {

	private static final String TAG = "AppCommunicationAgent";
	private static final int HOUSEKEEPER_CLEAN = 0;

	// Delay after which a proxy is removed from the pool
	private static final int HOUSEKEEPER_DELAY = 15 * 1000;

	private Map<String, AppServiceProxy> mProxies;
	private Context mContext;
	private boolean mShutdown;
	private Handler mHousekeeper;
	private final Handler.Callback mHousekeeperCallback = new Handler.Callback() {

		@Override
		public boolean handleMessage(Message msg) {
			switch (msg.what) {
			case HOUSEKEEPER_CLEAN:
				synchronized (this) {
					AppServiceProxy proxy = (AppServiceProxy) msg.obj;
					mContext.unbindService(proxy);
					mProxies.remove(proxy.getComponentName());
					Log.d(TAG, "Housekeeper removed the proxy to " + proxy.getComponentName());
				}
				return true;

			default:
				return false;
			}
		}
	};

	/**
	 * Creates a new {@link AppCommunicationAgent} that will use the given
	 * context to bind to app services.
	 *
	 * @param context
	 *            - the context used in binding
	 */
	public AppCommunicationAgent(Context context) {
		mContext = context;
		mProxies = new HashMap<>();

		// Dispatch Housekeeper messages on the main thread because threads from
		// the MW thread pool may get terminated once the execution request has
		// been completed
		mHousekeeper = new Handler(Looper.getMainLooper(), mHousekeeperCallback);
	}

	/**
	 * Returns an {@link AppServiceProxy} that can be used to communicate with
	 * the remote service identified by the given component name. If an alive
	 * proxy for the given component name is found in cache, it i returned.
	 * Otherwise, a new proxy is created, stored in the cache and returned.
	 *
	 * @param componentName
	 *            - the component name of the remote service
	 * @return a proxy to communicate with the remote service
	 */
	public AppServiceProxy getProxy(String componentName) {
		synchronized (this) {

			if (mShutdown) {
				throw new IllegalStateException("Communication agent is shutdown");
			}

			long start = System.nanoTime();

			if (mProxies.containsKey(componentName)) {
				AppServiceProxy proxy = mProxies.get(componentName);
				unscheduleHousekeeper(proxy);

				if (proxy.isConnected()) {
					long st = (System.nanoTime() - start) / 1000;
					Log.d(TAG, "HIT for " + componentName + " in " + st + "us");

					scheduleHousekeeper(proxy);
					return proxy;
				} else {
					Log.d(TAG, "Removed dead proxy (" + componentName + ")");
					mProxies.remove(proxy);
					// Go on with the creation of new proxy
				}
			}

			AppServiceProxy proxy = new AppServiceProxy(componentName);
			mProxies.put(componentName, proxy);
			Intent serviceIntent = new Intent();
			serviceIntent.setComponent(ComponentName.unflattenFromString(componentName));
			boolean bound = mContext.bindService(serviceIntent, proxy, Context.BIND_AUTO_CREATE);

			if (!bound) {
				Log.e(TAG, "Cannot bound to app service with intent " + componentName);
				return null;
			}

			long st = (System.nanoTime() - start) / 1000;
			Log.d(TAG, "MISS for " + componentName + " in " + st + "us");

			scheduleHousekeeper(proxy);
			return proxy;
		}
	}

	private void scheduleHousekeeper(AppServiceProxy proxy){
		mHousekeeper.sendMessageDelayed(mHousekeeper.obtainMessage(HOUSEKEEPER_CLEAN, proxy), HOUSEKEEPER_DELAY);
	}

	private void unscheduleHousekeeper(AppServiceProxy proxy){
		mHousekeeper.removeMessages(HOUSEKEEPER_CLEAN, proxy);
	}

	/**
	 * Unbinds all proxies available in caches and prevents the creation of new
	 * ones.
	 */
	public void shutdown() {
		synchronized (this) {
			if (mShutdown) {
				return;
			}

			for (AppServiceProxy p : mProxies.values()) {
				if (p.isConnected()) {
					mContext.unbindService(p);
					mProxies.remove(p);
				}
			}

			mShutdown = true;
		}
	}

	/**
	 * @return true if the agent has been shutdown and thus no more proxies can
	 *         be created.
	 */
	public boolean isShutDown() {
		return mShutdown;
	}
}


/**
 * The MIT License
 *
 * Copyright (C) 2015 Asterios Raptis
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package de.alpharogroup.xml.sax.handler;

import java.io.IOException;
import java.io.Writer;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import lombok.Getter;
import lombok.NonNull;

/**
 * The abstract class {@link WriterHandler}.
 */
public abstract class WriterHandler extends DefaultHandler
{

	/** The string builder. */
	private StringBuilder stringBuilder;

	/** The writer. */
	@Getter
	private final Writer writer;

	/**
	 * Instantiates a new {@link WriterHandler} object with the given {@link Writer}
	 *
	 * @param writer
	 *            the writer
	 */
	public WriterHandler(final @NonNull Writer writer)
	{
		this.writer = writer;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void characters(final char[] buf, final int offset, final int len) throws SAXException
	{
		final String string = new String(buf, offset, len);

		if (stringBuilder == null)
		{
			stringBuilder = new StringBuilder(string);
		}
		else
		{
			stringBuilder.append(string);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void endDocument() throws SAXException
	{
		insertNewLine();
		try
		{
			insertNewLine();
			writer.flush();
		}
		catch (final IOException e)
		{
			throw new SAXException("I/O error", e);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void endElement(final String namespaceURI, final String simpleName,
		final String qualifiedName) throws SAXException
	{
		writeToBuffer();

		String elementName = simpleName;

		if ("".equals(elementName))
		{
			elementName = qualifiedName;
		}

		write("</" + elementName + ">");
	}

	/**
	 * Insert a new line to the writer
	 *
	 * @throws SAXException
	 *             any SAX exception, possibly wrapping another exception
	 */
	private void insertNewLine() throws SAXException
	{
		try
		{
			writer.write(System.getProperty("line.separator"));
		}
		catch (final IOException e)
		{
			throw new SAXException("I/O error", e);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void startDocument() throws SAXException
	{
		write("<?xml version='1.0' encoding='UTF-8'?>");
		insertNewLine();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void startElement(final String namespaceURI, final String simpleName,
		final String qualifiedName, final Attributes attributes) throws SAXException
	{
		writeToBuffer();

		String elementName = simpleName;

		if ("".equals(elementName))
		{
			elementName = qualifiedName;
		}

		write("<" + elementName);

		if (attributes != null)
		{
			for (int i = 0; i < attributes.getLength(); i++)
			{
				String attributeName = attributes.getLocalName(i);

				if ("".equals(attributeName))
				{
					attributeName = attributes.getQName(i);
				}

				write(" ");
				write(attributeName + "=\"" + attributes.getValue(i) + "\"");
			}
		}

		write(">");
	}

	/**
	 * Write the given {@link String} object
	 *
	 * @param string
	 *            the string
	 * @throws SAXException
	 *             any SAX exception, possibly wrapping another exception
	 */
	protected abstract void write(final String string) throws SAXException;

	/**
	 * Write to buffer.
	 *
	 * @throws SAXException
	 *             any SAX exception, possibly wrapping another exception
	 */
	private void writeToBuffer() throws SAXException
	{
		if (stringBuilder == null)
		{
			return;
		}
		final String string = stringBuilder.toString().trim();
		write(string);
		stringBuilder = null;
	}
}


/**
 * The MIT License
 *
 * Copyright (C) 2015 Asterios Raptis
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package de.alpharogroup.xml.to.xsd;

import java.io.File;
import java.io.IOException;
import java.io.StringWriter;

import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;
import org.apache.xmlbeans.impl.inst2xsd.Inst2Xsd;
import org.apache.xmlbeans.impl.inst2xsd.Inst2XsdOptions;
import org.apache.xmlbeans.impl.xb.xsdschema.SchemaDocument;

import lombok.experimental.UtilityClass;

/**
 * The class {@link XmlToXsdExtensions} generates XML schema from XML files. It makes use of
 * XMLBeans tools.
 */
@UtilityClass
public final class XmlToXsdExtensions
{

	/**
	 * Returns a xsd String from the given XML file that can used for several purposes.
	 *
	 * @param xmlFile
	 *            the XML file
	 * @return the resulted xsd String
	 * @throws XmlException
	 *             the xml exception
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	public static String xmlToXsd(final File xmlFile) throws XmlException, IOException
	{
		return xmlToXsd(xmlFile, new Inst2XsdOptions());
	}

	/**
	 * Creates or update the given xsd output file from the given XML file.
	 *
	 * @param xmlInputFile
	 *            the xml input file
	 * @param xsdOutFile
	 *            the xsd out file
	 * @throws XmlException
	 *             the xml exception
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	public static void xmlToXsd(final File xmlInputFile, final File xsdOutFile)
		throws XmlException, IOException
	{
		xmlToXsd(xmlInputFile, xsdOutFile, new Inst2XsdOptions());
	}

	/**
	 * Creates or update the given xsd output file from the given XML file.
	 *
	 * @param xmlInputFile
	 *            the xml input file
	 * @param xsdOutFile
	 *            the xsd out file
	 * @param inst2XsdOptions
	 *            the inst2 xsd options
	 * @throws XmlException
	 *             occurs when a give xml file is invalid.
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	public static void xmlToXsd(final File xmlInputFile, final File xsdOutFile,
		final Inst2XsdOptions inst2XsdOptions) throws XmlException, IOException
	{
		xmlToXsd(xmlInputFile, xsdOutFile, inst2XsdOptions, new XmlOptions().setSavePrettyPrint());
	}

	/**
	 * Creates or update the given xsd output file from the given XML file.
	 *
	 * @param xmlFile
	 *            the xml file
	 * @param xsdOutFile
	 *            the xsd out file
	 * @param inst2XsdOptions
	 *            the inst2 xsd options
	 * @param xmlOptions
	 *            the xml options
	 * @throws XmlException
	 *             occurs when a give xml file is invalid.
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	public static void xmlToXsd(final File xmlFile, final File xsdOutFile,
		final Inst2XsdOptions inst2XsdOptions, final XmlOptions xmlOptions)
		throws XmlException, IOException
	{
		final XmlObject[] xmlInstances = new XmlObject[1];
		xmlInstances[0] = XmlObject.Factory.parse(xmlFile);
		final SchemaDocument[] schemaDocs = Inst2Xsd.inst2xsd(xmlInstances, inst2XsdOptions);
		final SchemaDocument schema = schemaDocs[0];
		schema.save(xsdOutFile, xmlOptions);
	}

	/**
	 * Returns a xsd String from the given XML file that can used for several purposes.
	 *
	 * @param xmlFile
	 *            the XML file.
	 * @param inst2XsdOptions
	 *            the inst2 xsd options
	 * @return the resulted xsd String
	 * @throws XmlException
	 *             occurs when a give xml file is invalid.
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	public static String xmlToXsd(final File xmlFile, final Inst2XsdOptions inst2XsdOptions)
		throws XmlException, IOException
	{
		return xmlToXsd(xmlFile, inst2XsdOptions, new XmlOptions().setSavePrettyPrint());
	}

	/**
	 * Returns a xsd String from the given XML file that can used for several purposes.
	 *
	 * @param xmlFile
	 *            the xml file
	 * @param inst2XsdOptions
	 *            the inst2 xsd options
	 * @param xmlOptions
	 *            the xml options
	 * @return the resulted xsd String
	 * @throws XmlException
	 *             occurs when a give xml file is invalid.
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	public static String xmlToXsd(final File xmlFile, final Inst2XsdOptions inst2XsdOptions,
		final XmlOptions xmlOptions) throws XmlException, IOException
	{
		final XmlObject[] xmlInstances = new XmlObject[1];
		xmlInstances[0] = XmlObject.Factory.parse(xmlFile);
		final SchemaDocument[] schemaDocs = Inst2Xsd.inst2xsd(xmlInstances, inst2XsdOptions);
		final SchemaDocument schema = schemaDocs[0];
		return schema.xmlText(xmlOptions);
	}

	/**
	 * Creates or update the given xsd output file from the given XML file.
	 *
	 * @param xmlFiles
	 *            the XML files
	 * @param inst2XsdOptions
	 *            the inst2 xsd options
	 * @param outDir
	 *            The directory path for output files. Default is '.' (the current directory).
	 * @param outPrefix
	 *            The prefix for output file names. Default is "schema".
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	public static void xmlToXsd(final File[] xmlFiles, final Inst2XsdOptions inst2XsdOptions,
		File outDir, String outPrefix) throws IOException
	{
		if (xmlFiles == null || xmlFiles.length == 0)
		{
			throw new IllegalArgumentException("XML file array can not be null and not empty.");
		}
		if (outDir == null)
		{
			outDir = new File(".");
		}
		if (outPrefix == null)
		{
			outPrefix = "schema";
		}
		final XmlObject[] xmlInstances = new XmlObject[xmlFiles.length];
		int i = 0;
		try
		{
			for (i = 0; i < xmlFiles.length; i++)
			{
				xmlInstances[i] = XmlObject.Factory.parse(xmlFiles[i]);
			}
		}
		catch (final XmlException e)
		{
			throw new IllegalArgumentException(
				"Invalid xml file: '" + xmlFiles[i].getName() + "'. \n" + e.getMessage(), e);
		}
		catch (final IOException e)
		{
			throw new IllegalArgumentException(
				"Could not read file: '" + xmlFiles[i].getName() + "'. " + e.getMessage(), e);
		}

		final SchemaDocument[] schemaDocs = Inst2Xsd.inst2xsd(xmlInstances, inst2XsdOptions);

		try
		{
			for (i = 0; i < schemaDocs.length; i++)
			{
				final SchemaDocument schema = schemaDocs[i];
				schema.save(new File(outDir, outPrefix + i + ".xsd"),
					new XmlOptions().setSavePrettyPrint());
			}
		}
		catch (final IOException e)
		{
			throw new IOException("Could not write file: '" + outDir + File.pathSeparator
				+ outPrefix + i + ".xsd" + "'. " + e.getMessage(), e);
		}
	}

	public static String xmlToXsd(final String xmlFile, final Inst2XsdOptions inst2XsdOptions,
		final XmlOptions xmlOptions) throws XmlException, IOException
	{
		final XmlObject[] xmlInstances = new XmlObject[1];
		xmlInstances[0] = XmlObject.Factory.parse(xmlFile);
		final SchemaDocument[] schemaDocs = Inst2Xsd.inst2xsd(xmlInstances, inst2XsdOptions);
		final SchemaDocument schema = schemaDocs[0];
		final StringWriter writer = new StringWriter();
		schema.save(writer, xmlOptions);
		return writer.toString();
	}
}


/**
 * The MIT License
 *
 * Copyright (C) 2015 Asterios Raptis
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package de.alpharogroup.xml.json;

import java.util.logging.Level;

import com.fasterxml.jackson.core.JsonProcessingException;

import lombok.experimental.UtilityClass;
import lombok.extern.java.Log;

/**
 * The class {@link ObjectToJsonQuietlyExtensions} converts java objects to json string objects in a
 * quietly manner as the name let presume.
 */
@UtilityClass
@Log
public class ObjectToJsonQuietlyExtensions
{

	/**
	 * Creates from the given Object a json string. Note if an exception occurs null will be
	 * returned.
	 *
	 * @param <T>
	 *            the generic type of the given argument
	 * @param object
	 *            the object.
	 * @return the json string or null if an error occured by parsing.
	 */
	public static <T> String toJsonQuietly(final T object)
	{
		try
		{
			return ObjectToJsonExtensions.toJson(object);
		}
		catch (final JsonProcessingException e)
		{
			log.log(Level.SEVERE,
				"An error occured when converting object to String.\nGiven object:"
					+ object.toString() + "\n",
				e);
		}
		return null;
	}
}


/*
 * Copyright 2014 Jacopo Aliprandi, Dario Archetti
 * Copyright 2015 Stefano Cappa
 *
 * This file is part of SPF.
 *
 * SPF is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * SPF 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 Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with SPF.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package it.polimi.spf.lib;

import it.polimi.spf.shared.SPFInfo;
import it.polimi.spf.shared.model.SPFError;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.IInterface;
import android.util.Log;

/**
 * Base class for SPF components. A component is wrappers for an AIDL interface
 * implementing SPF features like Services, Profile and Notification, and
 * provides high level functionalities to applications leveraging on SPF.
 * Components are loaded using an asynchronous pattern providing a callback that
 * is notified when the binding to the remote service is completed.
 *
 * Loading an SPF component is as simple as calling
 * {@link #load(Context, Descriptor, ConnectionCallback)}. Each concrete
 * component should provide its own load(Context, Callback) method
 * delegating to the one of the superclass, where Callback is a type-fixed
 * interface extending {@link ConnectionCallback}.
 *
 *

 Example of subclass:
 *
 *

 * public ExampleComponent extends Component<ExampleComponent, IExampleService> {
 *    ...
 * }
 *

 *
 * For such class, an example of {@link Descriptor} to be used in
 * {@link #load(Context, Descriptor, ConnectionCallback)} is

 *

 *
 *

 * private static final Descriptor<ExampleComponent, IExampleService> DESCRIPTOR = new Descriptor<ExampleComponent, IExampleService>() {
 *
 *     public String getActionName(){ ... }
 *
 *     public IExampleService castInterface(IBinder binder){
 *         return IExampleService.Stub.asInterface(binder);
 *     }
 *
 *     public ExampleComponentx createInstance(Context context, I serviceInterface, ServiceConnection connection, ConnectionCallback<ExampleComponent> callback){
 *         return new ExampleComponent(...);
 *     }
 * }
 *

 *
 * @author darioarchetti
 *
 * @param <C>
 *            the type of the Component concrete implementation
 * @param
 *            the type of the AIDL interface that is wrapped by the component
 */
public abstract class Component<C extends Component<C, I>, I extends IInterface> {

	/**
	 * Loads a local component asynchronously.
	 *
	 * @param context
	 *            - the context to use to bind to the service
	 * @param descriptor
	 *            - a {@link Descriptor} to handle the creation of the
	 *            component;
	 * @param callback
	 *            - the callback to be notified when the service is available
	 * @param <C>
	 *            the type of the Component concrete implementation
	 * @param
	 *            the type of the AIDL interface that is wrapped by the
	 *            component
	 */
	protected static <C extends Component<C, I>, I extends IInterface> void load(final Context context, final Descriptor<C, I> descriptor, final ConnectionCallback<C> callback) {

		Utils.notNull(context, "context must not be null");
		Utils.notNull(descriptor, "context must not be null");

		if (AccessTokenManager.get(context).hasToken()) {
			bindToService(context, descriptor, callback);
		} else {
			AccessTokenManager.get(context).requireAccessToken(context, new AccessTokenManager.RegistrationCallback() {

				@Override
				public void onRegistrationSuccessful() {
					Log.d("Component" , "Access Toekn: " + descriptor);
					bindToService(context, descriptor, callback);
				}

				@Override
				public void onRegistrationError(SPFError errorMsg) {
					callback.onError(errorMsg);
				}
			});
		}

	}

	/**
	 * Performs the binding to the remote service
	 *
	 * @param context
	 *            - the context used to bind to the service
	 * @param descriptor
	 *            - the {@link Descriptor} to handle the service
	 * @param callback
	 *            - the callback to notify of the service availability
	 */
	private static <C extends Component<C, I>, I extends IInterface> void bindToService(final Context context, final Descriptor<C, I> descriptor, final ConnectionCallback<C> callback) {

		Intent intent = new Intent();
		intent.setComponent(SPFInfo.getSPFServiceComponentName());
		intent.setAction(descriptor.getActionName());

		ServiceConnection connection = new ServiceConnection() {

			@Override
			public void onServiceConnected(ComponentName name, IBinder binder) {
				I service = descriptor.castInterface(binder);
				C instance = descriptor.createInstance(context, service, this, callback);
				callback.onServiceReady(instance);
			}

			@Override
			public void onServiceDisconnected(ComponentName name) {
				callback.onDisconnect();
			}
		};

		if (!context.bindService(intent, connection, Context.BIND_AUTO_CREATE)) {
			callback.onError(new SPFError(SPFError.SPF_NOT_INSTALLED_ERROR_CODE));
		}
	}

	private I mServiceInterface;
	private Context mContext;
	private ServiceConnection mConnection;
	private ConnectionCallback<C> mCallback;

	protected Component(Context context, I serviceInterface, ServiceConnection connection, final ConnectionCallback<C> callback) {
		this.mServiceInterface = serviceInterface;
		this.mContext = context;
		this.mConnection = connection;
		this.mCallback = callback;
	}

	/**
	 * Disconnects the component from the remote service.
	 *
	 * @see Context#unbindService(ServiceConnection)
	 */
	public void disconnect() {
		try {
			mContext.unbindService(mConnection);
		} catch (Exception e) {
			Log.w(getClass().getSimpleName(), "Exception unbinding from service: ", e);
		}
	}

	/**
	 * Returns the callback provided to
	 * {@link #load(Context, Descriptor, ConnectionCallback)}
	 *
	 * @return the callback
	 */
	protected ConnectionCallback<C> getCallback() {
		return mCallback;
	}

	/**
	 * Returns the service interface to which this component is bound.
	 *
	 * @return the service interface
	 */
	protected I getService() {
		return mServiceInterface;
	}

	/**
	 * The context used to bind to the service.
	 *
	 * @return the context
	 */
	protected Context getContext() {
		return mContext;
	}

	/**
	 * Returns the access token provided to this app by SPF upon registration.
	 *
	 * @return the access token
	 */
	protected String getAccessToken() {
		return AccessTokenManager.get(getContext()).getAccessToken();
	}

	/**
	 * Performs common error handling operations. Subclasses may override it to
	 * provide specific behavior.
	 *
	 * @param err
	 */
	protected void handleError(SPFError err) {
		if (err.codeEquals(SPFError.TOKEN_NOT_VALID_ERROR_CODE)) {
			AccessTokenManager.get(mContext).invalidateToken();
		}
		mCallback.onError(err);
	}

	/**
	 * Interface for callback objects that can be used to receive notification
	 * of the availability of the remote service.
	 *
	 * @author darioarchetti
	 *
	 * @param <C>
	 *            - the type of the {@link Component} implementation that this
	 *            class will be listen for availability.
	 */
	protected interface ConnectionCallback<C extends Component<C, ?>> {

		/**
		 * Called when the binding to the service has been completed and thus
		 * its services can be used. An instance of the component is provided to
		 * interact with the service.
		 *
		 * @param componentInstance
		 *            - the instance of the component
		 */
		public void onServiceReady(C componentInstance);

		/**
		 * Called when an error occurred in the connection. After this call, the
		 * component instance and all remote objects obtained from it should not
		 * be used anymore.
		 *
		 * @param err
		 *            - the error that occurred.
		 */
		public void onError(SPFError err);

		/**
		 * Called when the connection to the remote service has been terminated.
		 * After this call, the component instance and all remote objects
		 * obtained from it should not be used anymore.
		 */
		public void onDisconnect();
	}

	/**
	 * Contains utility method to handle the binding and the creation of a local
	 * component.
	 *
	 * @author darioarchetti
	 *
	 *
	 * @param <C>
	 *            the type of the Component concrete implementation
	 * @param
	 *            the type of the AIDL interface that is wrapped by the
	 *            component
	 */
	protected interface Descriptor<C extends Component<C, I>, I extends IInterface> {

		/**
		 * @return the action to be included in the intent when binding to
		 *         SPFService.
		 */
		public String getActionName();

		/**
		 * Casts a binder to the interface type handled by the component.
		 *
		 * @param binder
		 *            - the remote binder
		 * @return - the actual stub to communicate with the remote service
		 */
		public I castInterface(IBinder binder);

		/**
		 * Creates a new instance of the local component when the binding is
		 * completed and the stub is available.
		 *
		 * @param context
		 *            - the context used to bind to the service
		 * @param serviceInterface
		 *            - the stub of the remote service
		 * @param connection
		 *            - the service connection used to bind to the service,
		 *            needed for
		 *            {@link Context#unbindService(ServiceConnection)}
		 * @param callback
		 *            - the callback to be notified when an error occurs
		 * @return an instance of the component implementation.
		 */
		public C createInstance(Context context, I serviceInterface, ServiceConnection connection, ConnectionCallback<C> callback);
	}
}


/*
 * Copyright 2014 Jacopo Aliprandi, Dario Archetti
 *
 * This file is part of SPF.
 *
 * SPF is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * SPF 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 Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with SPF.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package it.polimi.spf.framework.notification;

import it.polimi.spf.shared.model.SPFQuery;
import it.polimi.spf.shared.model.SPFTrigger;

import java.util.List;
import java.util.Locale;
import java.util.Map;
import android.util.LongSparseArray;

/**
 * @author Jacopo Aliprandi
 *
 *         Contains all the triggers' instances. Handle the 'event processing'.
 *         Calls action performer when a trigger has been fired.
 *
 *         IMPORTANT it is not thread safe: to be called in the same
 *         handler/thread
 */
/*package*/ class SPFTriggerEngine {

	private LongSparseArray<SPFTrigger> triggers;
	private SPFActionPerformer actionPerformer;

	public SPFTriggerEngine(SPFActionPerformer performer) {
		this.triggers = new LongSparseArray<SPFTrigger>();
		this.actionPerformer = performer;

	}

	void remove(Long obj) {
		triggers.remove(obj);
	}

	void lookForMatchingTrigger(SPFAdvProfile profile) {
		for (int i = 0; i < triggers.size(); i++) {
			SPFTrigger trg = triggers.valueAt(i);
			boolean res = analize(trg.getQuery(), profile);
			if (res) {
				actionPerformer.perform(profile, trg);
			}
		}
	}

	private boolean analize(SPFQuery query, SPFAdvProfile profile) {
		// XXX #SearchRefactor
		// - Merge with search responder to create a unique query performer
		// - Fix collections: now i use contains(...) that works but is inefficient
		Map<String, String> fields = query.getProfileFields();
		for (String identifier : fields.keySet()) {
			String profileValue = profile.getField(identifier).toLowerCase(Locale.US);
			String queryValue = fields.get(identifier).toLowerCase(Locale.US);
			if (!profileValue.contains(queryValue)) {
				return false;
			}
		}

		for (String tag : query.getTags()) {
			if (!checkTag(profile, tag)) {
				return false;
			}
		}

		for (String app : query.getApps()) {
			if (!profile.getApplications().contains(app)) {
				return false;
			}
		}

		return true;
	}

	private boolean checkTag(SPFAdvProfile profile, String tag) {
		String _tag = tag.trim().toLowerCase(Locale.US);
		for (String value : profile.getFieldsValues()) {
			if (value.toLowerCase(Locale.US).contains(_tag)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Adds a new trigger. If exists a trigger with the same id of the one
	 * specified as parameter, it will be replaced.
	 *
	 * @param trigger
	 *            the trigger to add
	 */
	public void put(SPFTrigger trigger) {
		triggers.put(trigger.getId(), trigger);
	}

	/**
	 * Update the current set of triggers with the given list.
	 *
	 * @param triggers2
	 */
	public void refreshTriggers(List<SPFTrigger> triggers2) {
		triggers.clear();
		for (SPFTrigger trg : triggers2) {
			triggers.put(trg.getId(), trg);
		}
	}

}