/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
package org.libreoffice.impressremote.communication;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import android.content.Context;
import android.content.Intent;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;

import org.libreoffice.impressremote.util.Intents;

class TcpServersFinder implements ServersFinder, Runnable {
    private static final int SEARCH_DELAY_IN_SECONDS = 5;
    private static final int BLOCKING_TIMEOUT_IN_SECONDS = 10;

    private static final int SEARCH_RESULT_BUFFER_SIZE = 1024;

    private final Context mContext;

    private final Map<String, Server> mServers;

    private DatagramSocket mSearchSocket;
    private ScheduledExecutorService mSearchService;

    public TcpServersFinder(Context aContext) {
        mContext = aContext;

        mServers = new HashMap<String, Server>();

        mSearchService = Executors.newSingleThreadScheduledExecutor();
    }

    @Override
    public void startSearch() {
        stopSearch();

        mSearchService = Executors.newSingleThreadScheduledExecutor();
        mSearchService.scheduleAtFixedRate(this, 0, SEARCH_DELAY_IN_SECONDS, TimeUnit.SECONDS);
    }

    @Override
    public void run() {
        setUpSearchSocket();

        sendSearchCommand();

        listenForSearchResults();

        tearDownSearchSocket();
    }

    private void setUpSearchSocket() {
        try {
            mSearchSocket = new DatagramSocket();
            mSearchSocket.setSoTimeout((int) TimeUnit.SECONDS.toMillis(BLOCKING_TIMEOUT_IN_SECONDS));
        } catch (SocketException e) {
            throw new RuntimeException("Unable to open search socket.");
        }
    }

    private void sendSearchCommand() {
        try {
            mSearchSocket.send(buildSearchPacket());
        } catch (IOException e) {
            throw new RuntimeException("Unable to send search packet.");
        }
    }

    private DatagramPacket buildSearchPacket() {
        try {
            String aSearchCommand = Protocol.Commands
                .prepareCommand(Protocol.Commands.SEARCH_SERVERS);

            DatagramPacket aSearchPacket = new DatagramPacket(
                aSearchCommand.getBytes(), aSearchCommand.length());

            aSearchPacket.setAddress(
                InetAddress.getByName(Protocol.Addresses.SERVER_SEARCH));
            aSearchPacket.setPort(Protocol.Ports.SERVER_SEARCH);

            return aSearchPacket;
        } catch (UnknownHostException e) {
            throw new RuntimeException("Unable to find address to search.");
        }
    }

    private void listenForSearchResults() {
        DatagramPacket aSearchResultPacket = buildSearchResultPacket();

        String aSearchResult = receiveSearchResult(aSearchResultPacket);

        if (TextUtils.isEmpty(aSearchResult)) {
            return;
        }

        Scanner aSearchResultScanner = new Scanner(aSearchResult);

        String aMessage = aSearchResultScanner.nextLine();

        if (!Protocol.Messages.ADVERTISE.equals(aMessage)) {
            return;
        }

        String aFoundServerHostname = aSearchResultScanner.nextLine();

        addServer(buildServer(aSearchResultPacket, aFoundServerHostname));

        callUpdatingServersList();
    }

    private DatagramPacket buildSearchResultPacket() {
        byte[] aSearchResultBuffer = new byte[SEARCH_RESULT_BUFFER_SIZE];

        return new DatagramPacket(
            aSearchResultBuffer, aSearchResultBuffer.length);
    }

    private String receiveSearchResult(DatagramPacket aSearchResultPacket) {
        try {
            mSearchSocket.receive(aSearchResultPacket);

            return new String(aSearchResultPacket.getData(), Protocol.CHARSET);
        } catch (SocketTimeoutException e) {
            return "";
        } catch (IOException e) {
            throw new RuntimeException("Unable to receive search result.");
        }
    }

    private void addServer(Server aServer) {
        mServers.put(aServer.getAddress(), aServer);
    }

    private Server buildServer(DatagramPacket aSearchResultPacket, String aServerHostname) {
        String aServerAddress = aSearchResultPacket.getAddress().getHostAddress();

        return Server.newTcpInstance(aServerAddress, aServerHostname);
    }

    private void callUpdatingServersList() {
        Intent aIntent = Intents.buildServersListChangedIntent();
        LocalBroadcastManager.getInstance(mContext).sendBroadcast(aIntent);
    }

    private void tearDownSearchSocket() {
        mSearchSocket.close();
    }

    @Override
    public void stopSearch() {
        mSearchService.shutdown();
    }

    @Override
    public List<Server> getServers() {
        return new ArrayList<Server>(mServers.values());
    }
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

@@ -4206,29 +4206,16 @@
 mPacket(
-%0A            
 aSearchR
@@ -4609,70 +4609,17 @@
     
-throw new RuntimeException(%22Unable to receive search result.%22)
+return %22%22
 ;%0A  

Fix possible unexpected search stop.