/* -*- 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 android.content.Context;
import android.content.Intent;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;

public class TcpServersFinder implements ServersFinder, Runnable {
    private static final int SEARCH_DELAY_IN_MILLISECONDS = 1000 * 10;
    private static final int BLOCKING_TIMEOUT_IN_MILLISECONDS = 1000 * 10;

    private static final int SEARCH_RESULT_BUFFER_SIZE = 1024;

    private final Context mContext;

    private final Map<String, Server> mServers;

    private DatagramSocket mSearchSocket;
    private Thread mSearchResultsListenerThread;
    private boolean mSearchStopRequested;

    public TcpServersFinder(Context aContext) {
        mContext = aContext;

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

    @Override
    public void startSearch() {
        if (mSearchResultsListenerThread != null) {
            return;
        }

        mSearchStopRequested = false;

        mSearchResultsListenerThread = new Thread(this);
        mSearchResultsListenerThread.start();
    }

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

        while (!mSearchStopRequested) {
            sendSearchCommand();

            listenForSearchResults();

            setUpSearchDelay();
        }

        tearDownSearchSocket();
    }

    private void setUpSearchSocket() {
        try {
            mSearchSocket = new DatagramSocket();
            mSearchSocket.setSoTimeout(BLOCKING_TIMEOUT_IN_MILLISECONDS);
        } 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();

        Server aFoundServer = new Server(Server.Protocol.TCP,
            aSearchResultPacket.getAddress().getHostAddress(),
            aFoundServerHostname);

        addServer(aFoundServer);

        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 void callUpdatingServersList() {
        Intent aServersListUpdatedIntent = new Intent(
            CommunicationService.MSG_SERVERLIST_CHANGED);

        LocalBroadcastManager.getInstance(mContext)
            .sendBroadcast(aServersListUpdatedIntent);
    }

    private void setUpSearchDelay() {
        try {
            Thread.sleep(SEARCH_DELAY_IN_MILLISECONDS);
        } catch (InterruptedException e) {
            mSearchStopRequested = true;
        }
    }

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

    @Override
    public void stopSearch() {
        if (mSearchResultsListenerThread == null) {
            return;
        }

        mSearchStopRequested = true;

        mSearchResultsListenerThread = null;
    }

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

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

@@ -745,16 +745,147 @@
 Scanner;
+%0Aimport java.util.concurrent.Executors;%0Aimport java.util.concurrent.ScheduledExecutorService;%0Aimport java.util.concurrent.TimeUnit;
 %0A%0Aimport
@@ -1138,29 +1138,24 @@
 CH_DELAY_IN_
-MILLI
 SECONDS = 10
@@ -1147,31 +1147,24 @@
 IN_SECONDS =
- 1000 *
  10;%0A    pri
@@ -1440,85 +1440,47 @@
 ate 
-Thread mSearchResultsListenerThread;%0A    private boolean mSearchStopRequested
+ScheduledExecutorService mSearchService
 ;%0A%0A 
@@ -1671,222 +1671,164 @@
     
-if (
 mSearch
-ResultsListenerThread != null) %7B%0A            return;%0A        %7D%0A%0A        mSearchStopRequested = false;%0A%0A        mSearchResultsListenerThread = new Thread(this);%0A        mSearchResultsListenerThread.start(
+Service = Executors.newSingleThreadScheduledExecutor();%0A        mSearchService.scheduleAtFixedRate(this, 0, SEARCH_DELAY_IN_SECONDS, TimeUnit.SECONDS
 );%0A 
@@ -1905,52 +1905,8 @@
 );%0A%0A
-        while (!mSearchStopRequested) %7B%0A    
     
@@ -1927,28 +1927,24 @@
 Command();%0A%0A
-    
         list
@@ -1970,51 +1970,8 @@
 );%0A%0A
-            setUpSearchDelay();%0A        %7D%0A%0A
     
@@ -5044,217 +5044,8 @@
  %7D%0A%0A
-    private void setUpSearchDelay() %7B%0A        try %7B%0A            Thread.sleep(SEARCH_DELAY_IN_MILLISECONDS);%0A        %7D catch (InterruptedException e) %7B%0A            mSearchStopRequested = true;%0A        %7D%0A    %7D%0A%0A
     
@@ -5177,164 +5177,33 @@
     
-if (
 mSearch
-ResultsListenerThread == null) %7B%0A            return;%0A        %7D%0A%0A        mSearchStopRequested = true;%0A%0A        mSearchResultsListenerThread = null
+Service.shutdown()
 ;%0A  

Change thread usage at TcpServersFinder.