/*
 * Copyright 2015 Anton Tananaev (anton.tananaev@gmail.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.traccar.client;

import android.content.Context;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.PowerManager;
import android.preference.PreferenceManager;
import android.util.Log;

public class TrackingController implements PositionProvider.PositionListener, NetworkManager.NetworkHandler {

    private static final String TAG = TrackingController.class.getSimpleName();
    private static final int RETRY_DELAY = 30 * 1000;
    private static final int WAKE_LOCK_TIMEOUT = 60 * 1000;

    private boolean isOnline;
    private boolean isWaiting;

    private Context context;
    private Handler handler;
    private SharedPreferences preferences;

    private PositionProvider positionProvider;
    private DatabaseHelper databaseHelper;
    private NetworkManager networkManager;

    private PowerManager.WakeLock wakeLock;

    public TrackingController(Context context) {
        this.context = context;
        handler = new Handler();
        preferences = PreferenceManager.getDefaultSharedPreferences(context);
        positionProvider = new PositionProvider(context, this);
        databaseHelper = new DatabaseHelper(context);
        networkManager = new NetworkManager(context, this);
        isOnline = networkManager.isOnline();

        PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName());
    }

    public void start() {
        if (isOnline) {
            read();
        }
        positionProvider.startUpdates();
        networkManager.start();
    }

    public void stop() {
        networkManager.stop();
        positionProvider.stopUpdates();
        handler.removeCallbacksAndMessages(null);
    }

    @Override
    public void onPositionUpdate(Position position) {
        if (position != null) {
            StatusActivity.addMessage(context.getString(R.string.status_location_update));
            write(position);
        }
    }

    @Override
    public void onNetworkUpdate(boolean isOnline) {
        if (!this.isOnline && isOnline) {
            read();
        }
        this.isOnline = isOnline;
    }

    //
    // State transition examples:
    //
    // write -> read -> send -> delete -> read
    //
    // read -> send -> retry -> read -> send
    //

    private void log(String action, Position position) {
        if (position != null) {
            action += " (" +
                    "id:" + position.getId() +
                    " time:" + position.getTime().getTime() / 1000 +
                    " lat:" + position.getLatitude() +
                    " lon:" + position.getLongitude() + ")";
        }
        Log.d(TAG, action);
    }

    private void write(Position position) {
        log("write", position);
        wakeLock.acquire(WAKE_LOCK_TIMEOUT);
        databaseHelper.insertPositionAsync(position, new DatabaseHelper.DatabaseHandler<Void>() {
            @Override
            public void onSuccess(Void result) {
                if (isOnline && isWaiting) {
                    read();
                    isWaiting = false;
                }
                wakeLock.release();
            }

            @Override
            public void onFailure(RuntimeException error) {
                wakeLock.release();
            }
        });
    }

    private void read() {
        log("read", null);
        wakeLock.acquire(WAKE_LOCK_TIMEOUT);
        databaseHelper.selectPositionAsync(new DatabaseHelper.DatabaseHandler<Position>() {
            @Override
            public void onSuccess(Position result) {
                if (result != null) {
                    send(result);
                } else {
                    isWaiting = true;
                }
                wakeLock.release();
            }

            @Override
            public void onFailure(RuntimeException error) {
                retry();
                wakeLock.release();
            }
        });
    }

    private void delete(Position position) {
        log("delete", position);
        wakeLock.acquire(WAKE_LOCK_TIMEOUT);
        databaseHelper.deletePositionAsync(position.getId(), new DatabaseHelper.DatabaseHandler<Void>() {
            @Override
            public void onSuccess(Void result) {
                read();
                wakeLock.release();
            }

            @Override
            public void onFailure(RuntimeException error) {
                retry();
                wakeLock.release();
            }
        });
    }

    private void send(final Position position) {
        log("send", position);
        wakeLock.acquire(WAKE_LOCK_TIMEOUT);
        String request = ProtocolFormatter.formatRequest(
                preferences.getString(MainActivity.KEY_ADDRESS, null),
                Integer.parseInt(preferences.getString(MainActivity.KEY_PORT, null)),
                position);

        RequestManager.sendRequestAsync(request, new RequestManager.RequestHandler() {
            @Override
            public void onSuccess() {
                delete(position);
                wakeLock.release();
            }

            @Override
            public void onFailure() {
                StatusActivity.addMessage(context.getString(R.string.status_send_fail));
                retry();
                wakeLock.release();
            }
        });
    }

    private void retry() {
        log("retry", null);
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                if (isOnline) {
                    read();
                }
            }
        }, RETRY_DELAY);
    }

}

@@ -1500,16 +1500,204 @@
 eLock;%0A%0A
+    private void lock() %7B%0A        wakeLock.acquire(WAKE_LOCK_TIMEOUT);%0A    %7D%0A%0A    private void unlock() %7B%0A        if (wakeLock.isHeld()) %7B%0A            wakeLock.release();%0A        %7D%0A    %7D%0A%0A
     publ
@@ -1733,24 +1733,24 @@
  context) %7B%0A
-
         this
@@ -3678,42 +3678,13 @@
     
-wakeLock.acquire(WAKE_LOCK_TIMEOUT
+lock(
 );%0A 
@@ -3997,32 +3997,22 @@
         
-wakeLock.release
+unlock
 ();%0A    
@@ -4120,32 +4120,22 @@
         
-wakeLock.release
+unlock
 ();%0A    
@@ -4228,42 +4228,13 @@
     
-wakeLock.acquire(WAKE_LOCK_TIMEOUT
+lock(
 );%0A 
@@ -4568,32 +4568,22 @@
         
-wakeLock.release
+unlock
 ();%0A    
@@ -4716,32 +4716,22 @@
         
-wakeLock.release
+unlock
 ();%0A    
@@ -4849,42 +4849,13 @@
     
-wakeLock.acquire(WAKE_LOCK_TIMEOUT
+lock(
 );%0A 
@@ -5070,32 +5070,22 @@
         
-wakeLock.release
+unlock
 ();%0A    
@@ -5218,32 +5218,22 @@
         
-wakeLock.release
+unlock
 ();%0A    
@@ -5353,42 +5353,13 @@
     
-wakeLock.acquire(WAKE_LOCK_TIMEOUT
+lock(
 );%0A 
@@ -5797,32 +5797,22 @@
         
-wakeLock.release
+unlock
 ();%0A    
@@ -6012,32 +6012,22 @@
         
-wakeLock.release
+unlock
 ();%0A    

Fix wake lock release issue