package cgeo.geocaching.speech;

import cgeo.geocaching.DirectionProvider;
import cgeo.geocaching.geopoint.Geopoint;
import cgeo.geocaching.utils.GeoDirHandler;
import cgeo.geocaching.utils.Log;

import org.apache.commons.lang3.StringUtils;

import android.app.Activity;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.OnInitListener;

import java.util.Locale;

/**
 * Service to speak the compass directions.
 *
 */
public class SpeechService extends Service implements OnInitListener {

    private static final int SPEECH_PAUSE_SECONDS = 30;
    private static final String EXTRA_TARGET_COORDS = "target";
    private static Activity startingActivity;
    private static boolean isRunning = false;
    /**
     * Text to speech API of Android
     */
    private TextToSpeech tts;
    /**
     * TTS has been initialized and we can speak.
     */
    private boolean initialized = false;
    protected float direction;
    protected Geopoint position;
    protected boolean directionInitialized;
    protected boolean positionInitialized;

    GeoDirHandler geoHandler = new GeoDirHandler() {
        @Override
        protected void updateDirection(float newDirection) {
            direction = DirectionProvider.getDirectionNow(startingActivity, newDirection);
            directionInitialized = true;
            updateCompass();
        }

        @Override
        protected void updateGeoData(cgeo.geocaching.IGeoData newGeo) {
            position = newGeo.getCoords();
            positionInitialized = true;
            if (newGeo.getSpeed() > 5) {
                direction = newGeo.getBearing();
                directionInitialized = true;
            }
            updateCompass();
        }
    };
    /**
     * remember when we talked the last time
     */
    private long lastSpeechTime = 0;
    private Geopoint target;

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    protected void updateCompass() {
        // make sure we have both sensor values before talking
        if (!positionInitialized || !directionInitialized) {
            return;
        }

        // avoid any calculation, if the delay since the last output is not long enough
        long now = System.currentTimeMillis();
        if (now - lastSpeechTime <= SPEECH_PAUSE_SECONDS * 1000) {
            return;
        }

        final String text = TextFactory.getText(position, target, direction);
        if (StringUtils.isNotEmpty(text)) {
            speak(text);
        }
    }

    @Override
    public void onCreate() {
        super.onCreate();
        tts = new TextToSpeech(this, this);
    }

    @Override
    public void onDestroy() {
        geoHandler.stopGeoAndDir();
        if (tts != null) {
            tts.stop();
            tts.shutdown();
        }
        super.onDestroy();
    }

    @Override
    public void onInit(int status) {
        // The text to speech system takes some time to initialize.
        if (status != TextToSpeech.SUCCESS) {
            Log.e("Text to speech cannot be initialized.");
            return;
        }

        int switchLocale = tts.setLanguage(Locale.getDefault());

        if (switchLocale == TextToSpeech.LANG_MISSING_DATA
                || switchLocale == TextToSpeech.LANG_NOT_SUPPORTED) {
            Log.e("Current languge not supported by text to speech.");
            return;
        }

        initialized = true;

        geoHandler.startGeoAndDir();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (intent != null) {
            target = intent.getParcelableExtra(EXTRA_TARGET_COORDS);
        }
        return START_NOT_STICKY;
    }

    private void speak(final String text) {
        if (!initialized) {
            return;
        }
        lastSpeechTime = System.currentTimeMillis();
        tts.speak(text, TextToSpeech.QUEUE_FLUSH, null);
    }

    public static void startService(final Activity activity, Geopoint dstCoords) {
        isRunning = true;
        startingActivity = activity;
        Intent talkingService = new Intent(activity, SpeechService.class);
        talkingService.putExtra(EXTRA_TARGET_COORDS, dstCoords);
        activity.startService(talkingService);
    }

    public static void stopService(final Activity activity) {
        isRunning = false;
        activity.stopService(new Intent(activity, SpeechService.class));
    }

    public static boolean isRunning() {
        return isRunning;
    }

}

@@ -627,32 +627,93 @@
 inal int SPEECH_
+MINPAUSE_SECONDS = 5;%0A    private static final int SPEECH_MAX
 PAUSE_SECONDS = 
@@ -1984,16 +1984,61 @@
 me = 0;%0A
+    private float lastSpeechDistance = 0.0f;%0A
     priv
@@ -2432,16 +2432,22 @@
         
+final 
 long now
@@ -2520,16 +2520,19 @@
  SPEECH_
+MIN
 PAUSE_SE
@@ -2590,156 +2590,1046 @@
     
-final String text = TextFactory.getText(position, target, direction);%0A        if (StringUtils.isNotEmpty(text)) %7B%0A            speak(text);%0A        %7D
+// to speak, we want max pause to have elapsed or distance to geopoint to have changed by a given amount%0A        final float distance = position.distanceTo(target);%0A        if (now - lastSpeechTime %3C= SPEECH_MAXPAUSE_SECONDS * 1000) %7B%0A            if (Math.abs(lastSpeechDistance - distance) %3C getDeltaForDistance(distance)) %7B%0A                return;%0A            %7D%0A        %7D%0A%0A        final String text = TextFactory.getText(position, target, direction);%0A        if (StringUtils.isNotEmpty(text)) %7B%0A            lastSpeechTime = System.currentTimeMillis();%0A            lastSpeechDistance = distance;%0A            speak(text);%0A        %7D%0A    %7D%0A%0A    /**%0A     * Return distance required to be moved based on overall distance.%3Cbr%3E%0A     *%0A     * @param distance%0A     *            in km%0A     * @return delta in km%0A     */%0A    private static float getDeltaForDistance(final float distance) %7B%0A        if (distance %3E 1.0) %7B%0A            return 0.2f;%0A        %7D else if (distance %3E 0.05) %7B%0A            return distance / 5.0f;%0A        %7D%0A%0A        return 0f;
 %0A   
@@ -4921,61 +4921,8 @@
   %7D%0A
-        lastSpeechTime = System.currentTimeMillis();%0A
     

Fix #2716 - Adjust compass speech rate based on distance