/*****************************************************************************
 * DebugLogService.java
 *****************************************************************************
 * Copyright © 2015 VLC authors and VideoLAN
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version. 
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. 
 *****************************************************************************/

package org.videolan.vlc.gui;

import android.app. Notification;
import android.app. PendingIntent;
import android.app. Service;
import android.content. ComponentName;
import android.content. Context;
import android.content. Intent;
import android.content. ServiceConnection;
import android.os. Handler;
import android.os. IBinder;
import android.os. Looper;
import android.os. RemoteCallbackList;
import android.os. RemoteException;
import android.support.v4.app. NotificationCompat;
import android.text.format. DateFormat;

import org.videolan.vlc. R;
import org.videolan.vlc.util. AndroidDevices;
import org.videolan.vlc.util. Logcat;
import org.videolan.vlc.util. Util;

import java.io. BufferedWriter;
import java.io. FileNotFoundException;
import java.io. FileOutputStream;
import java.io. IOException;
import java.io. OutputStreamWriter;
import java.util. LinkedList;
import java.util. List;

public class DebugLogService extends Service implements Logcat. Callback, Runnable {

    private static final int MSG_STARTED = 0;
    private static final int MSG_STOPPED = 1;
    private static final int MSG_ONLOG = 2;
    private static final int MSG_SAVED = 3;

    private static final int MAX_LINES = 20000;

    private Logcat mLogcat = null;
    private LinkedList<String> mLogList = new LinkedList<String>();
    private Thread mSaveThread = null;
    private final RemoteCallbackList<IDebugLogServiceCallback> mCallbacks = new RemoteCallbackList<IDebugLogServiceCallback>();
    private final IBinder mBinder = new DebugLogServiceStub(this);

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

    static class DebugLogServiceStub extends IDebugLogService. Stub {
        private DebugLogService mService;
        DebugLogServiceStub(DebugLogService service) {
            mService = service;
        }
        public void start() {
            mService.start();
        }
        public void stop() {
            mService.stop();
        }
        public void clear() {
            mService.clear();
        }
        public void save() {
            mService.save();
        }
        public void registerCallback(IDebugLogServiceCallback cb) {
            mService.registerCallback(cb);
        }
        public void unregisterCallback(IDebugLogServiceCallback cb) {
            mService.unregisterCallback(cb);
        }
    }

    private synchronized void sendMessage(int what, String str) {
        int i = mCallbacks.beginBroadcast();
        while (i > 0) {
            i--;
            final IDebugLogServiceCallback cb = mCallbacks.getBroadcastItem(i);
            try {
                switch (what) {
                case MSG_STOPPED:
                    cb.onStopped();
                    break;
                case MSG_STARTED: {
                    cb.onStarted(mLogList);
                    break;
                }
                case MSG_ONLOG:
                    cb.onLog(str);
                    break;
                case MSG_SAVED:
                    cb.onSaved(str ! = null ?  true : false, str);
                    break;
                }
            } catch (RemoteException e) {
            }
        }
        mCallbacks.finishBroadcast();
    }

    @Override
    public synchronized void onLog(String log) {
        if (mLogList.size() > MAX_LINES)
            mLogList.remove(0);
        mLogList.add(log);
        sendMessage(MSG_ONLOG, log);
    }

    public synchronized void start() {
        if (mLogcat ! = null)
            return;
        clear();
        mLogcat = new Logcat();
        mLogcat.start(this);

        final Intent debugLogIntent = new Intent(this, DebugLogActivity.class);
        debugLogIntent.setAction("android.intent.action. MAIN");
        debugLogIntent.setFlags(Intent. FLAG_ACTIVITY_SINGLE_TOP|Intent. FLAG_ACTIVITY_CLEAR_TOP);
        final PendingIntent pi = PendingIntent.getActivity(this, 0, debugLogIntent, 0);

        final NotificationCompat. Builder builder = new NotificationCompat. Builder(this);
        builder.setContentTitle(getResources().getString(R.string.log_service_title));
        builder.setContentText(getResources().getString(R.string.log_service_text));
        builder.setSmallIcon(R.drawable.ic_stat_vlc);
        builder.setContentIntent(pi);
        final Notification notification = builder.build();
        startForeground(R.string.log_service_title, notification);

        startService(new Intent(this, DebugLogService.class));
        sendMessage(MSG_STARTED, null);
    }

    public synchronized void stop() {
        mLogcat.stop();
        mLogcat = null;
        sendMessage(MSG_STOPPED, null);
        stopForeground(true);
        stopSelf();
    }

    public synchronized void clear() {
        mLogList.clear();
    }

    /* mSaveThread */
    @Override
    public void run() {
        final CharSequence timestamp = DateFormat.format(
                "yyyyMMdd_kkmmss", System.currentTimeMillis());
        final String filename = AndroidDevices. EXTERNAL_PUBLIC_DIRECTORY + "/vlc_logcat_" + timestamp + ".log";
        boolean saved = true;
        FileOutputStream fos = null;
        OutputStreamWriter output = null;
        BufferedWriter bw = null;

        try {
            fos = new FileOutputStream(filename);
            output = new OutputStreamWriter(fos);
            bw = new BufferedWriter(output);
            synchronized (this) {
                for (String line : mLogList) {
                    bw.write(line);
                    bw.newLine();
                }
            }
        } catch (FileNotFoundException e) {
            saved = false;
        } catch (IOException ioe) {
            saved = false;
        } finally {
            saved &= Util.close(bw);
            saved &= Util.close(output);
            saved &= Util.close(fos);
        }
        synchronized (this) {
            mSaveThread = null;
            sendMessage(MSG_SAVED, saved ?  filename : null);
        }
    }

    public synchronized void save() {
        if (mSaveThread ! = null) {
            try {
                mSaveThread.join();
            } catch (InterruptedException e) {}
            mSaveThread = null;
        }
        mSaveThread = new Thread(this);
        mSaveThread.start();
    }

    public int onStartCommand(Intent intent, int flags, int startId) {
        return START_STICKY;
    }

    private void registerCallback(IDebugLogServiceCallback cb) {
        if (cb ! = null) {
            mCallbacks.register(cb);
            sendMessage(mLogcat ! = null ?  MSG_STARTED : MSG_STOPPED, null);
        }
    }

    private void unregisterCallback(IDebugLogServiceCallback cb) {
        if (cb ! = null)
            mCallbacks.unregister(cb);
    }

    public static class Client {

        public interface Callback {
            void onStarted(List<String> lostList);
            void onStopped();
            void onLog(String msg);
            void onSaved(boolean success, String path);
        }

        private boolean mBound = false;
        private final Context mContext;
        private Callback mCallback;
        private IDebugLogService mIDebugLogService;
        private Handler mHandler;

        private final IDebugLogServiceCallback. Stub mICallback = new IDebugLogServiceCallback. Stub() {
            @Override
            public void onStopped() throws RemoteException {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        mCallback.onStopped();
                    }
                });
            }
            @Override
            public void onStarted(final List<String> logList) throws RemoteException {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        mCallback.onStarted(logList);
                    }
                });
            }
            @Override
            public void onLog(final String msg) throws RemoteException {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        mCallback.onLog(msg);
                    }
                });
            }
            @Override
            public void onSaved(final boolean success, final String path) throws RemoteException {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        mCallback.onSaved(success, path);
                    }
                });
            }
        };

        private final ServiceConnection mServiceConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                synchronized (Client.this) {
                    mIDebugLogService = IDebugLogService. Stub.asInterface(service);
                    try {
                        mIDebugLogService.registerCallback(mICallback);
                    } catch (RemoteException e) {
                        release();
                        mContext.stopService(new Intent(mContext, DebugLogService.class));
                        mCallback.onStopped();
                    }
                }
            }
            @Override
            public void onServiceDisconnected(ComponentName name) {
                release();
                mContext.stopService(new Intent(mContext, DebugLogService.class));
                mCallback.onStopped();
            }
        };

        public Client(Context context, Callback cb) throws IllegalArgumentException {
            if (context == null | cb == null)
                throw new IllegalArgumentException("Context and Callback can't be null");

            mContext = context;
            mCallback = cb;
            mHandler = new Handler(Looper.getMainLooper());
            mBound = mContext.bindService(new Intent(mContext, DebugLogService.class), mServiceConnection, Context. BIND_AUTO_CREATE);
        }

        public boolean start() {
            synchronized (this) {
                if (mIDebugLogService ! = null) {
                    try {
                        mIDebugLogService.start();
                        return true;
                    } catch (RemoteException e) {
                    }
                }
                return false;
            }
        }
        public boolean stop() {
            synchronized (this) {
                if (mIDebugLogService ! = null) {
                    try {
                        mIDebugLogService.stop();
                        return true;
                    } catch (RemoteException e) {
                    }
                }
                return false;
            }
        }
        public boolean clear() {
            synchronized (this) {
                if (mIDebugLogService ! = null) {
                    try {
                        mIDebugLogService.clear();
                        return true;
                    } catch (RemoteException e) {
                    }
                }
                return false;
            }
        }
        public boolean save() {
            synchronized (this) {
                if (mIDebugLogService ! = null) {
                    try {
                        mIDebugLogService.save();
                        return true;
                    } catch (RemoteException e) {
                    }
                }
                return false;
            }
        }
        public void release() {
            if (mBound) {
                synchronized (this) {
                    if (mIDebugLogService ! = null && mICallback ! = null) {
                        try {
                            mIDebugLogService.unregisterCallback(mICallback);
                        } catch (RemoteException e) {
                        }
                        mIDebugLogService = null;
                    }
                }
                mBound = false;
                mContext.unbindService(mServiceConnection);
            }
            mHandler.removeCallbacksAndMessages(null);
        }
    }
}