/*
 * This is the source code of Telegram for Android v. 3.x.x.
 * It is licensed under GNU GPL v. 2 or later.
 * You should have received a copy of the license in this archive (see LICENSE).
 *
 * Copyright Nikolai Kudashov, 2013-2017.
 */

package org.telegram.messenger;

import android.annotation.TargetApi;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Shader;
import android.media.MediaDescription;
import android.media.MediaMetadata;
import android.media.browse.MediaBrowser;
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Process;
import android.os.SystemClock;
import android.service.media.MediaBrowserService;
import android.text.TextUtils;
import android.util.SparseArray;

import org.telegram.SQLite.SQLiteCursor;
import org.telegram.messenger.audioinfo.AudioInfo;
import org.telegram.tgnet.NativeByteBuffer;
import org.telegram.tgnet.TLRPC;
import org.telegram.ui.LaunchActivity;

import java.io.File;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class MusicBrowserService extends MediaBrowserService implements NotificationCenter.NotificationCenterDelegate {

    private static final String SLOT_RESERVATION_SKIP_TO_NEXT = "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_NEXT";
    private static final String SLOT_RESERVATION_SKIP_TO_PREV = "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_PREVIOUS";
    private static final String SLOT_RESERVATION_QUEUE = "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_QUEUE";

    private MediaSession mediaSession;
    private static final String MEDIA_ID_ROOT = "__ROOT__";

    private int currentAccount = UserConfig.selectedAccount; //TODO account
    private boolean chatsLoaded;
    private boolean loadingChats;
    private ArrayList<Integer> dialogs = new ArrayList<>();
    private SparseArray<TLRPC.User> users = new SparseArray<>();
    private SparseArray<TLRPC.Chat> chats = new SparseArray<>();
    private SparseArray<ArrayList<MessageObject>> musicObjects = new SparseArray<>();
    private SparseArray<ArrayList<MediaSession.QueueItem>> musicQueues = new SparseArray<>();

    public static final String ACTION_CMD = "com.example.android.mediabrowserservice.ACTION_CMD";
    public static final String CMD_NAME = "CMD_NAME";
    public static final String CMD_PAUSE = "CMD_PAUSE";

    private Paint roundPaint;
    private RectF bitmapRect;

    private boolean serviceStarted;

    private int lastSelectedDialog;

    private static final int STOP_DELAY = 30000;

    private DelayedStopHandler delayedStopHandler = new DelayedStopHandler(this);

    @Override
    public void onCreate() {
        super.onCreate();
        ApplicationLoader.postInitApplication();

        lastSelectedDialog = MessagesController.getNotificationsSettings(currentAccount).getInt("auto_lastSelectedDialog", 0);

        mediaSession = new MediaSession(this, "MusicService");
        setSessionToken(mediaSession.getSessionToken());
        mediaSession.setCallback(new MediaSessionCallback());
        mediaSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);

        Context context = getApplicationContext();
        Intent intent = new Intent(context, LaunchActivity.class);
        PendingIntent pi = PendingIntent.getActivity(context, 99, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        mediaSession.setSessionActivity(pi);

        Bundle extras = new Bundle();
        extras.putBoolean(SLOT_RESERVATION_QUEUE, true);
        extras.putBoolean(SLOT_RESERVATION_SKIP_TO_PREV, true);
        extras.putBoolean(SLOT_RESERVATION_SKIP_TO_NEXT, true);
        mediaSession.setExtras(extras);

        updatePlaybackState(null);

        NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.messagePlayingPlayStateChanged);
        NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.messagePlayingDidStarted);
        NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.messagePlayingDidReset);
    }

    @Override
    public int onStartCommand(Intent startIntent, int flags, int startId) {
        /*if (startIntent != null) {
            String action = startIntent.getAction();
            String command = startIntent.getStringExtra(CMD_NAME);
            if (ACTION_CMD.equals(action)) {
                if (CMD_PAUSE.equals(command)) {
                    if (mPlayback != null && mPlayback.isPlaying()) {
                        handlePauseRequest();
                    }
                }
            }
        }*/
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        handleStopRequest(null);
        delayedStopHandler.removeCallbacksAndMessages(null);
        mediaSession.release();
    }

    @Override
    public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
        if (clientPackageName == null || Process.SYSTEM_UID != clientUid && Process.myUid() != clientUid && !clientPackageName.equals("com.google.android.mediasimulator") && !clientPackageName.equals("com.google.android.projection.gearhead")) {
            return null;
        }
        return new BrowserRoot(MEDIA_ID_ROOT, null);
    }

    @Override
    public void onLoadChildren(final String parentMediaId, final Result<List<MediaBrowser.MediaItem>> result) {
        if (!chatsLoaded) {
            result.detach();
            if (loadingChats) {
                return;
            }
            loadingChats = true;
            final MessagesStorage messagesStorage = MessagesStorage.getInstance(currentAccount);
            messagesStorage.getStorageQueue().postRunnable(new Runnable() {
                @Override
                public void run() {
                    try {
                        ArrayList<Integer> usersToLoad = new ArrayList<>();
                        ArrayList<Integer> chatsToLoad = new ArrayList<>();
                        SQLiteCursor cursor = messagesStorage.getDatabase().queryFinalized(String.format(Locale.US, "SELECT DISTINCT uid FROM media_v2 WHERE uid != 0 AND mid > 0 AND type = %d", DataQuery.MEDIA_MUSIC));
                        while (cursor.next()) {
                            int lower_part = (int) cursor.longValue(0);
                            if (lower_part == 0) {
                                continue;
                            }
                            dialogs.add(lower_part);
                            if (lower_part > 0) {
                                usersToLoad.add(lower_part);
                            } else {
                                chatsToLoad.add(-lower_part);
                            }
                        }
                        cursor.dispose();
                        if (!dialogs.isEmpty()) {
                            String ids = TextUtils.join(",", dialogs);
                            cursor = messagesStorage.getDatabase().queryFinalized(String.format(Locale.US, "SELECT uid, data, mid FROM media_v2 WHERE uid IN (%s) AND mid > 0 AND type = %d ORDER BY date DESC, mid DESC", ids, DataQuery.MEDIA_MUSIC));
                            while (cursor.next()) {
                                NativeByteBuffer data = cursor.byteBufferValue(1);
                                if (data != null) {
                                    TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false);
                                    message.readAttachPath(data, UserConfig.getInstance(currentAccount).clientUserId);
                                    data.reuse();
                                    if (MessageObject.isMusicMessage(message)) {
                                        int did = cursor.intValue(0);
                                        message.id = cursor.intValue(2);
                                        message.dialog_id = did;
                                        ArrayList<MessageObject> arrayList = musicObjects.get(did);
                                        ArrayList<MediaSession.QueueItem> arrayList1 = musicQueues.get(did);
                                        if (arrayList == null) {
                                            arrayList = new ArrayList<>();
                                            musicObjects.put(did, arrayList);
                                            arrayList1 = new ArrayList<>();
                                            musicQueues.put(did, arrayList1);
                                        }
                                        MessageObject messageObject = new MessageObject(currentAccount, message, false);
                                        arrayList.add(0, messageObject);
                                        MediaDescription.Builder builder = new MediaDescription.Builder().setMediaId(did + "_" + arrayList.size());
                                        builder.setTitle(messageObject.getMusicTitle());
                                        builder.setSubtitle(messageObject.getMusicAuthor());
                                        arrayList1.add(0, new MediaSession.QueueItem(builder.build(), arrayList1.size()));
                                    }
                                }
                            }
                            cursor.dispose();
                            if (!usersToLoad.isEmpty()) {
                                ArrayList<TLRPC.User> usersArrayList = new ArrayList<>();
                                messagesStorage.getUsersInternal(TextUtils.join(",", usersToLoad), usersArrayList);
                                for (int a = 0; a < usersArrayList.size(); a++) {
                                    TLRPC.User user = usersArrayList.get(a);
                                    users.put(user.id, user);
                                }
                            }
                            if (!chatsToLoad.isEmpty()) {
                                ArrayList<TLRPC.Chat> chatsArrayList = new ArrayList<>();
                                messagesStorage.getChatsInternal(TextUtils.join(",", chatsToLoad), chatsArrayList);
                                for (int a = 0; a < chatsArrayList.size(); a++) {
                                    TLRPC.Chat chat = chatsArrayList.get(a);
                                    chats.put(chat.id, chat);
                                }
                            }
                        }
                    } catch (Exception e) {
                        FileLog.e(e);
                    }
                    AndroidUtilities.runOnUIThread(new Runnable() {
                        @Override
                        public void run() {
                            chatsLoaded = true;
                            loadingChats = false;
                            loadChildrenImpl(parentMediaId, result);
                            if (lastSelectedDialog == 0 && !dialogs.isEmpty()) {
                                lastSelectedDialog = dialogs.get(0);
                            }
                            if (lastSelectedDialog != 0) {
                                ArrayList<MessageObject> arrayList = musicObjects.get(lastSelectedDialog);
                                ArrayList<MediaSession.QueueItem> arrayList1 = musicQueues.get(lastSelectedDialog);
                                if (arrayList != null && !arrayList.isEmpty()) {
                                    mediaSession.setQueue(arrayList1);
                                    if (lastSelectedDialog > 0) {
                                        TLRPC.User user = users.get(lastSelectedDialog);
                                        if (user != null) {
                                            mediaSession.setQueueTitle(ContactsController.formatName(user.first_name, user.last_name));
                                        } else {
                                            mediaSession.setQueueTitle("DELETED USER");
                                        }
                                    } else {
                                        TLRPC.Chat chat = chats.get(-lastSelectedDialog);
                                        if (chat != null) {
                                            mediaSession.setQueueTitle(chat.title);
                                        } else {
                                            mediaSession.setQueueTitle("DELETED CHAT");
                                        }
                                    }
                                    MessageObject messageObject = arrayList.get(0);
                                    MediaMetadata.Builder builder = new MediaMetadata.Builder();
                                    builder.putLong(MediaMetadata.METADATA_KEY_DURATION, messageObject.getDuration() * 1000);
                                    builder.putString(MediaMetadata.METADATA_KEY_ARTIST, messageObject.getMusicAuthor());
                                    builder.putString(MediaMetadata.METADATA_KEY_TITLE, messageObject.getMusicTitle());
                                    mediaSession.setMetadata(builder.build());
                                }
                            }
                            updatePlaybackState(null);
                        }
                    });
                }
            });
        } else {
            loadChildrenImpl(parentMediaId, result);
        }
    }

    private void loadChildrenImpl(final String parentMediaId, final Result<List<MediaBrowser.MediaItem>> result) {
        List<MediaBrowser.MediaItem> mediaItems = new ArrayList<>();

        if (MEDIA_ID_ROOT.equals(parentMediaId)) {
            for (int a = 0; a < dialogs.size(); a++) {
                int did = dialogs.get(a);
                MediaDescription.Builder builder = new MediaDescription.Builder().setMediaId("__CHAT_" + did);
                TLRPC.FileLocation avatar = null;
                if (did > 0) {
                    TLRPC.User user = users.get(did);
                    if (user != null) {
                        builder.setTitle(ContactsController.formatName(user.first_name, user.last_name));
                        if (user.photo != null && user.photo.photo_small instanceof TLRPC.TL_fileLocation) {
                            avatar = user.photo.photo_small;
                        }
                    } else {
                        builder.setTitle("DELETED USER");
                    }
                } else {
                    TLRPC.Chat chat = chats.get(-did);
                    if (chat != null) {
                        builder.setTitle(chat.title);
                        if (chat.photo != null && chat.photo.photo_small instanceof TLRPC.TL_fileLocation) {
                            avatar = chat.photo.photo_small;
                        }
                    } else {
                        builder.setTitle("DELETED CHAT");
                    }
                }
                Bitmap bitmap = null;
                if (avatar != null) {
                    bitmap = createRoundBitmap(FileLoader.getPathToAttach(avatar, true));
                    if (bitmap != null) {
                        builder.setIconBitmap(bitmap);
                    }
                }
                if (avatar == null || bitmap == null) {
                    builder.setIconUri(Uri.parse("android.resource://" + getApplicationContext().getPackageName() + "/drawable/contact_blue"));
                }
                mediaItems.add(new MediaBrowser.MediaItem(builder.build(), MediaBrowser.MediaItem.FLAG_BROWSABLE));
            }
        } else if (parentMediaId != null && parentMediaId.startsWith("__CHAT_")) {
            int did = 0;
            try {
                did = Integer.parseInt(parentMediaId.replace("__CHAT_", ""));
            } catch (Exception e) {
                FileLog.e(e);
            }
            ArrayList<MessageObject> arrayList = musicObjects.get(did);
            if (arrayList != null) {
                for (int a = 0; a < arrayList.size(); a++) {
                    MessageObject messageObject = arrayList.get(a);
                    MediaDescription.Builder builder = new MediaDescription.Builder().setMediaId(did + "_" + a);
                    builder.setTitle(messageObject.getMusicTitle());
                    builder.setSubtitle(messageObject.getMusicAuthor());
                    mediaItems.add(new MediaBrowser.MediaItem(builder.build(), MediaBrowser.MediaItem.FLAG_PLAYABLE));
                }
            }
        }
        result.sendResult(mediaItems);
    }

    private Bitmap createRoundBitmap(File path) {
        try {
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inSampleSize = 2;
            Bitmap bitmap = BitmapFactory.decodeFile(path.toString(), options);
            if (bitmap != null) {
                Bitmap result = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
                result.eraseColor(Color.TRANSPARENT);
                Canvas canvas = new Canvas(result);
                BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
                if (roundPaint == null) {
                    roundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
                    bitmapRect = new RectF();
                }
                roundPaint.setShader(shader);
                bitmapRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight());
                canvas.drawRoundRect(bitmapRect, bitmap.getWidth(), bitmap.getHeight(), roundPaint);
                return result;
            }
        } catch (Throwable e) {
            FileLog.e(e);
        }
        return null;
    }

    private final class MediaSessionCallback extends MediaSession.Callback {
        @Override
        public void onPlay() {
            MessageObject messageObject = MediaController.getInstance().getPlayingMessageObject();
            if (messageObject == null) {
                onPlayFromMediaId(lastSelectedDialog + "_" + 0, null);
            } else {
                MediaController.getInstance().playMessage(messageObject);
            }
        }

        @Override
        public void onSkipToQueueItem(long queueId) {
            MediaController.getInstance().playMessageAtIndex((int) queueId);
            handlePlayRequest();
        }

        @Override
        public void onSeekTo(long position) {
            MessageObject messageObject = MediaController.getInstance().getPlayingMessageObject();
            if (messageObject != null) {
                MediaController.getInstance().seekToProgress(messageObject, position / 1000 / (float) messageObject.getDuration());
            }
        }

        @Override
        public void onPlayFromMediaId(String mediaId, Bundle extras) {
            String args[] = mediaId.split("_");
            if (args.length != 2) {
                return;
            }
            try {
                int did = Integer.parseInt(args[0]);
                int id = Integer.parseInt(args[1]);
                ArrayList<MessageObject> arrayList = musicObjects.get(did);
                ArrayList<MediaSession.QueueItem> arrayList1 = musicQueues.get(did);
                if (arrayList == null || id < 0 || id >= arrayList.size()) {
                    return;
                }
                lastSelectedDialog = did;
                MessagesController.getNotificationsSettings(currentAccount).edit().putInt("auto_lastSelectedDialog", did).commit();
                MediaController.getInstance().setPlaylist(arrayList, arrayList.get(id), false);
                mediaSession.setQueue(arrayList1);
                if (did > 0) {
                    TLRPC.User user = users.get(did);
                    if (user != null) {
                        mediaSession.setQueueTitle(ContactsController.formatName(user.first_name, user.last_name));
                    } else {
                        mediaSession.setQueueTitle("DELETED USER");
                    }
                } else {
                    TLRPC.Chat chat = chats.get(-did);
                    if (chat != null) {
                        mediaSession.setQueueTitle(chat.title);
                    } else {
                        mediaSession.setQueueTitle("DELETED CHAT");
                    }
                }
            } catch (Exception e) {
                FileLog.e(e);
            }
            handlePlayRequest();
        }

        @Override
        public void onPause() {
            handlePauseRequest();
        }

        @Override
        public void onStop() {
            handleStopRequest(null);
        }

        @Override
        public void onSkipToNext() {
            MediaController.getInstance().playNextMessage();
        }

        @Override
        public void onSkipToPrevious() {
            MediaController.getInstance().playPreviousMessage();
        }

        @Override
        public void onPlayFromSearch(String query, Bundle extras) {
            if (query == null || query.length() == 0) {
                return;
            }
            query = query.toLowerCase();
            for (int a = 0; a < dialogs.size(); a++) {
                int did = dialogs.get(a);
                if (did > 0) {
                    TLRPC.User user = users.get(did);
                    if (user == null) {
                        continue;
                    }
                    if (user.first_name != null && user.first_name.startsWith(query) || user.last_name != null && user.last_name.startsWith(query)) {
                        onPlayFromMediaId(did + "_" + 0, null);
                        break;
                    }
                } else {
                    TLRPC.Chat chat = chats.get(-did);
                    if (chat == null) {
                        continue;
                    }
                    if (chat.title != null && chat.title.toLowerCase().contains(query)) {
                        onPlayFromMediaId(did + "_" + 0, null);
                        break;
                    }
                }
            }
        }
    }

    private void updatePlaybackState(String error) {
        long position = PlaybackState.PLAYBACK_POSITION_UNKNOWN;
        MessageObject playingMessageObject = MediaController.getInstance().getPlayingMessageObject();
        if (playingMessageObject != null) {
            position = playingMessageObject.audioProgressSec * 1000;
        }

        PlaybackState.Builder stateBuilder = new PlaybackState.Builder().setActions(getAvailableActions());
        int state;
        if (playingMessageObject == null) {
            state = PlaybackState.STATE_STOPPED;
        } else {
            if (MediaController.getInstance().isDownloadingCurrentMessage()) {
                state = PlaybackState.STATE_BUFFERING;
            } else {
                state = MediaController.getInstance().isMessagePaused() ? PlaybackState.STATE_PAUSED : PlaybackState.STATE_PLAYING;
            }
        }

        if (error != null) {
            stateBuilder.setErrorMessage(error);
            state = PlaybackState.STATE_ERROR;
        }
        stateBuilder.setState(state, position, 1.0f, SystemClock.elapsedRealtime());
        if (playingMessageObject != null) {
            stateBuilder.setActiveQueueItemId(MediaController.getInstance().getPlayingMessageObjectNum());
        } else {
            stateBuilder.setActiveQueueItemId(0);
        }

        mediaSession.setPlaybackState(stateBuilder.build());
    }

    private long getAvailableActions() {
        long actions = PlaybackState.ACTION_PLAY | PlaybackState.ACTION_PLAY_FROM_MEDIA_ID | PlaybackState.ACTION_PLAY_FROM_SEARCH;
        MessageObject playingMessageObject = MediaController.getInstance().getPlayingMessageObject();
        if (playingMessageObject != null) {
            if (!MediaController.getInstance().isMessagePaused()) {
                actions |= PlaybackState.ACTION_PAUSE;
            }
            actions |= PlaybackState.ACTION_SKIP_TO_PREVIOUS;
            actions |= PlaybackState.ACTION_SKIP_TO_NEXT;
        }
        return actions;
    }

    private void handleStopRequest(String withError) {
        delayedStopHandler.removeCallbacksAndMessages(null);
        delayedStopHandler.sendEmptyMessageDelayed(0, STOP_DELAY);
        updatePlaybackState(withError);
        stopSelf();
        serviceStarted = false;
        NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.messagePlayingPlayStateChanged);
        NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.messagePlayingDidStarted);
        NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.messagePlayingDidReset);
    }

    private void handlePlayRequest() {
        delayedStopHandler.removeCallbacksAndMessages(null);
        if (!serviceStarted) {
            try {
                startService(new Intent(getApplicationContext(), MusicBrowserService.class));
            } catch (Throwable e) {
                FileLog.e(e);
            }
            serviceStarted = true;
        }

        if (!mediaSession.isActive()) {
            mediaSession.setActive(true);
        }

        MessageObject messageObject = MediaController.getInstance().getPlayingMessageObject();
        if (messageObject == null) {
            return;
        }
        MediaMetadata.Builder builder = new MediaMetadata.Builder();
        builder.putLong(MediaMetadata.METADATA_KEY_DURATION, messageObject.getDuration() * 1000);
        builder.putString(MediaMetadata.METADATA_KEY_ARTIST, messageObject.getMusicAuthor());
        builder.putString(MediaMetadata.METADATA_KEY_TITLE, messageObject.getMusicTitle());
        AudioInfo audioInfo = MediaController.getInstance().getAudioInfo();
        if (audioInfo != null) {
            Bitmap bitmap = audioInfo.getCover();
            if (bitmap != null) {
                builder.putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, bitmap);
            }
        }
        mediaSession.setMetadata(builder.build());
    }

    private void handlePauseRequest() {
        MediaController.getInstance().pauseMessage(MediaController.getInstance().getPlayingMessageObject());
        delayedStopHandler.removeCallbacksAndMessages(null);
        delayedStopHandler.sendEmptyMessageDelayed(0, STOP_DELAY);
    }

    @Override
    public void didReceivedNotification(int id, int account, Object... args) {
        updatePlaybackState(null);
        handlePlayRequest();
    }

    private static class DelayedStopHandler extends Handler {
        private final WeakReference<MusicBrowserService> mWeakReference;

        private DelayedStopHandler(MusicBrowserService service) {
            mWeakReference = new WeakReference<>(service);
        }

        @Override
        public void handleMessage(Message msg) {
            MusicBrowserService service = mWeakReference.get();
            if (service != null) {
                MessageObject messageObject = MediaController.getInstance().getPlayingMessageObject();
                if (messageObject != null && !MediaController.getInstance().isMessagePaused()) {
                    return;
                }
                service.stopSelf();
                service.serviceStarted = false;
            }
        }
    }
}