package de.tudarmstadt.informatik.hostage.sync.wifi_direct;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.NetworkInfo;
import android.net.wifi.WpsInfo;
import android.net.wifi.p2p.WifiP2pConfig;
import android.net.wifi.p2p.WifiP2pDevice;
import android.net.wifi.p2p.WifiP2pDeviceList;
import android.net.wifi.p2p.WifiP2pInfo;
import android.net.wifi.p2p.WifiP2pManager;
import android.util.Log;


import java.util.ArrayList;
import java.util.List;

/**
 * Created by Julien on 07.01.2015.
 */
public class WiFiP2pBroadcastReceiver extends BroadcastReceiver implements WifiP2pManager.PeerListListener, WifiP2pManager.ConnectionInfoListener {


    /**
     * This listener will inform about any wifi direct change.
     */
    public interface WiFiP2pBroadcastListener {
        public void discoveredDevices(List<WifiP2pDevice> peers);
        public void wifiP2pIsEnabled(boolean enabled);
        public void didConnect(boolean isHost, WifiP2pInfo connectionInfo);
        public void failedToConnect();
        public void didDisconnect();
        public void failedToDisconnect();
        public void ownDeviceInformationIsUpdated(WifiP2pDevice device);
    }

    private WifiP2pManager manager;
    private WifiP2pManager.Channel channel;

    private boolean isConnecting;

    public WifiP2pDevice getOwnDevice() {
        return ownDevice;
    }

    private WifiP2pDevice ownDevice;

    private android.net.NetworkInfo.DetailedState networkState = null;

    static boolean setIsWifiP2pEnabled;

    static boolean isConnected = false;

    private WiFiP2pBroadcastListener eventListener;

    /**
     * @param manager WifiP2pManager system service
     * @param channel Wifi p2p channel
     * @param listener WiFiP2pBroadcastListener
     */
    public WiFiP2pBroadcastReceiver(WifiP2pManager manager,
                                    WifiP2pManager.Channel channel,
                                    WiFiP2pBroadcastListener listener) {
        super();
        this.manager = manager;
        this.channel = channel;
        this.eventListener = listener;
    }

    /*
     * (non-Javadoc)
     * @see android.content.BroadcastReceiver#onReceive(android.content.Context,
     * android.content.Intent)
     */
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {

            // UI update to indicate wifi p2p status.
            int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1);
            if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) {
                // Wifi Direct mode is enabled
                setIsWifiP2pEnabled = (true);
            } else {
                setIsWifiP2pEnabled =(false);
            }
            this.eventListener.wifiP2pIsEnabled(setIsWifiP2pEnabled);

        } else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {
            // THE DEVICE LIST CHANGED
            // REQUEST THE LIST OF DEVICES
            Log.d("DEBUG_WiFiP2pBroadcastReceiver", "P2P peers changed.");
            if (manager != null) {
                manager.requestPeers(channel, this);
            }
        } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {

            if (manager == null) {
                return;
            }

            NetworkInfo networkInfo = (NetworkInfo) intent.getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);

            if (networkInfo.isConnected()) {
                isConnected = true;
                // we are connected with the other device, request connection
                // info to find group owner IP
                manager.requestConnectionInfo(channel, this);
                if (this.ownDevice == null){
                    this.disconnect();
                }
            } else {
                if (networkInfo.getDetailedState() == android.net.NetworkInfo.DetailedState.DISCONNECTED){
                    isConnected = false;
                }
                if (this.networkState != null && !this.networkState.equals(networkInfo.getDetailedState()) && networkInfo.getDetailedState() == android.net.NetworkInfo.DetailedState.DISCONNECTED){
                    // It's a disconnect
                    this.eventListener.didDisconnect();
                }
            }
            if (this.networkState != networkInfo.getDetailedState()){
                Log.d("DEBUG_WiFiP2pBroadcastReceiver", "P2P device network state changed to " + this.getDeviceNetworkStatus(networkInfo.getDetailedState()) + ".");
            }
            this.networkState = networkInfo.getDetailedState();
        } else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {
            WifiP2pDevice device = intent.getParcelableExtra( WifiP2pManager.EXTRA_WIFI_P2P_DEVICE);
            this.ownDevice = device;
            this.eventListener.ownDeviceInformationIsUpdated(device);
        }
    }


    // CONNECTION TO A DEVICE
    // ConnectionInfoListener
    @Override
    public void onConnectionInfoAvailable(final WifiP2pInfo info) {

        //
        // The owner IP is now known.
        //boolean thisDeviceIsHost = info.isGroupOwner;
        // InetAddress from WifiP2pInfo struct.
        //String ownerIP = info.groupOwnerAddress.getHostAddress();

        // After the group negotiation, we assign the group owner as the file
        // server. The file server is single threaded, single connection server
        // socket.

        if (info.groupFormed){
            isConnecting = false;
            this.eventListener.didConnect(info.isGroupOwner, info);
        }
    }

    // AVAILABLE DEVICES
    // PEERLISTLISTENER
    @Override
    public void onPeersAvailable(WifiP2pDeviceList peerList) {

        List<WifiP2pDevice> peers = new ArrayList<WifiP2pDevice>();
        peers.addAll(peerList.getDeviceList());

        if (peers.size() == 0) {
            Log.d("DEBUG_WiFiP2pBroadcastReceiver", "No devices found");
        }

        this.eventListener.discoveredDevices(peers);
        // DISMISS PROGRESS IF NEEDED
    }

    /**
     * Connects to the given device.
     * @param device
     */
    public void connect(WifiP2pDevice device){
        if (device != null) {
            WifiP2pConfig config = new WifiP2pConfig();
            config.deviceAddress = device.deviceAddress;
            config.wps.setup = WpsInfo.PBC;
            isConnecting = true;
            manager.connect(channel, config, new WifiP2pManager.ActionListener() {
                private WiFiP2pBroadcastListener eventListener;
                @Override
                public void onSuccess() {
                    isConnected = true;
                    // WiFiDirectBroadcastReceiver will notify us. Ignore for now.
                }

                @Override
                public void onFailure(int reason) {
                    isConnecting = false;
                    this.eventListener.failedToConnect();
                }

                public WifiP2pManager.ActionListener init(WiFiP2pBroadcastListener eventListener){
                    this.eventListener = eventListener;
                    return this;
                }
            }.init(this.eventListener));

            TimeoutTask abort_task = new TimeoutTask(20, new BackgroundTask.BackgroundTaskCompletionListener() {
                private WiFiP2pBroadcastReceiver receiver;
                @Override
                public void didSucceed() {
                    if (this.receiver.getOwnDevice().status != WifiP2pDevice.CONNECTED && isConnecting){
                        this.receiver.disconnect();
                        Log.d("DEBUG_WiFiP2pBroadcastReceiver", "Cancel connection process.");
                        isConnecting = false;
                    }
                }
                @Override
                public void didFail() {
                    // do nothing here
                }
                public BackgroundTask.BackgroundTaskCompletionListener init(WiFiP2pBroadcastReceiver receiver){
                    this.receiver = receiver;
                    return this;
                }
            }.init(this));
            abort_task.execute();
        }
    }

    /**
     * Disconnects from the connected wifi direct group if the own device is still connected.
     */
    public void disconnect() {
		if (this.getOwnDevice() == null) return; // no wifi connect

        if (isConnected||this.getOwnDevice().status == WifiP2pDevice.CONNECTED){
            manager.removeGroup(channel, new WifiP2pManager.ActionListener() {
                private WiFiP2pBroadcastListener eventListener;

                @Override
                public void onFailure(int reasonCode) {
                    isConnecting = false;
                    this.eventListener.failedToDisconnect();
                }

                @Override
                public void onSuccess() {
                    isConnected = false;
                    isConnecting = false;
                    this.eventListener.didDisconnect();
                }

                public WifiP2pManager.ActionListener init(WiFiP2pBroadcastListener eventListener){
                    this.eventListener = eventListener;
                    return this;
                }
            }.init(this.eventListener));
        }else {
            if (isConnecting){
                manager.cancelConnect(channel, new WifiP2pManager.ActionListener() {
                    private WiFiP2pBroadcastListener eventListener;
                    @Override
                    public void onSuccess() {
                        this.eventListener.failedToConnect();
                        isConnecting = false;
                    }

                    @Override
                    public void onFailure(int i) {
                        this.eventListener.failedToConnect();
                        isConnecting = false;
                    }
                    public WifiP2pManager.ActionListener init(WiFiP2pBroadcastListener listener){
                        this.eventListener = listener;
                        return this;
                    }
                }.init(this.eventListener));
            }
        }
    }

    /**
     * Discover other devices.
     * The WiFiP2pBroadcastListener will inform about any change.
     */
    public void discoverDevices(){
        manager.discoverPeers(channel, new WifiP2pManager.ActionListener() {
            @Override
            public void onSuccess() {
                Log.d("DEBUG_WiFiP2pBroadcastReceiver", " Discovering Peers initiated.");
            }
            @Override
            public void onFailure(int reasonCode) {
                Log.d("DEBUG_WiFiP2pBroadcastReceiver", " Discovering Peers failed. c="+reasonCode);
            }
        });
    }

    /**
     * Returns a string representation of the given network status.
     * @param networkStatus network status
     * @return localised network status string
     */
    private String getDeviceNetworkStatus(android.net.NetworkInfo.DetailedState networkStatus) {
        switch (networkStatus) {
            case DISCONNECTED: {
                return "Disconnected";
            }
            case AUTHENTICATING:
            {
                return "Authenticating";
            }
            case BLOCKED:
            {
                return "Blocked";
            }
            case CONNECTED: {
                return "CONNECTED";
            }
            case CONNECTING:
            {
                return "Connecting";
            }
            case DISCONNECTING:
            {
                return "Disconnecting";
            }
            case FAILED:
            {
                return "Failed";
            }
            case IDLE:
            {
                return "IDLE";
            }
            case OBTAINING_IPADDR:
            {
                return "Obtaining IPADDR";
            }
            case SCANNING:
            {
                return "Scanning";
            }
            case SUSPENDED:
            {
                return "Suspended";
            }
            default: {
                return "Unknown";
            }
        }
    }
}
