/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * 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 com.android.tv.mdnsoffloadcmd;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.RemoteException;
import android.widget.Toast;
import android.util.Log;

import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;

import java.util.HexFormat;

import device.google.atv.mdns_offload.IMdnsOffloadManager;

public class MdnsOffloadCmdService extends Service {
    private static final String TAG = MdnsOffloadCmdService.class.getSimpleName();

    private static final String ACTION_OFFLOAD_COMMAND =
            "com.android.tv.mdnsoffloadcmd.OFFLOAD_COMMAND";

    private static final String CHANNEL_ID = "MdnsOffloadCmdService";

    private IMdnsOffloadManager mMdnsOffloadManagerService;
    private IBinder mBinder;

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        //We don't want any app to bind to this service.
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        mBinder = new Binder();
        setupCommandBroadcastReceiver();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        createNotificationChannel();

        Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setContentTitle("Foreground Service")
                .setSmallIcon(R.drawable.notification_template_icon_low_bg)
                .build();

        bindMdnsOffloadManager();

        startForeground(1, notification);
        return START_NOT_STICKY;
    }

    private void createNotificationChannel() {
        NotificationChannel serviceChannel = new NotificationChannel(
                CHANNEL_ID,
                "Foreground Service Channel",
                NotificationManager.IMPORTANCE_DEFAULT
        );

        NotificationManager manager = getSystemService(NotificationManager.class);
        manager.createNotificationChannel(serviceChannel);
    }

    private void registerProtocolResponse(String rawHexPacket, String iface) {
        Log.d(TAG, "Registering on iface{" + iface + "} :" + rawHexPacket);
        IMdnsOffloadManager.OffloadServiceInfo info =
                new IMdnsOffloadManager.OffloadServiceInfo();
        info.rawOffloadPacket = HexFormat.of().parseHex(rawHexPacket);
        try {
            if (mMdnsOffloadManagerService == null) {
                Log.e(TAG, "Offload Manager not connected");
                return;
            }
            int recordKey = mMdnsOffloadManagerService.addProtocolResponses(iface, info, mBinder);
            Log.d(TAG, "Packet offloaded with recordKey=" + recordKey);
        } catch (RemoteException e) {
            Log.e(TAG, "Error while registering debug packet", e);
        }
    }

    private void registerPassthrough(String qname, String iface) {
        Log.d(TAG, "Registering on iface{" + iface + "} passthrough:" + qname);
        try {
            if (mMdnsOffloadManagerService == null) {
                Log.e(TAG, "Offload Manager not connected");
                return;
            }
            mMdnsOffloadManagerService.addToPassthroughList(iface, qname, mBinder);
        } catch (RemoteException e) {
            Log.e(TAG, "Error while adding passthrough qname", e);
        }
    }

    private void removeProtocolResponse(int recordKey) {
        IMdnsOffloadManager.OffloadServiceInfo info =
                new IMdnsOffloadManager.OffloadServiceInfo();
        try {
            if (mMdnsOffloadManagerService == null) {
                Log.e(TAG, "Offload Manager not connected");
                return;
            }
            mMdnsOffloadManagerService.removeProtocolResponses(recordKey, mBinder);
            Log.d(TAG, "Removed record " + recordKey);
        } catch (RemoteException e) {
            Log.e(TAG, "Error while registering debug packet", e);
        }
    }

    private void removePassthrough(String qname, String iface) {
        Log.d(TAG, "Removing passthrough:" + qname + " on iface{" + iface + "}");
        try {
            if (mMdnsOffloadManagerService == null) {
                Log.e(TAG, "Offload Manager not connected");
                return;
            }
            mMdnsOffloadManagerService.removeFromPassthroughList(iface, qname, mBinder);
        } catch (RemoteException e) {
            Log.e(TAG, "Error while removing passthrough qname", e);
        }
    }

    private void setupCommandBroadcastReceiver() {
        BroadcastReceiver receiver = new CommandBroadcastReceiver();
        IntentFilter filter = new IntentFilter();
        filter.addAction(ACTION_OFFLOAD_COMMAND);
        registerReceiver(receiver, filter, ContextCompat.RECEIVER_EXPORTED);
    }

    private class CommandBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getStringExtra("action");
            switch (action) {
                case "ADD_OFFLOAD": {
                    String iface = intent.getStringExtra("iface");
                    String rawHexPacket = intent.getStringExtra("raw_hex_packet");
                    if (rawHexPacket != null && iface != null) {
                        registerProtocolResponse(rawHexPacket, iface);
                    } else {
                        Log.d(TAG, "Bad parameters for ADD_OFFLOAD command");
                    }
                    break;
                }
                case "REMOVE_OFFLOAD": {
                    int recordKey = intent.getIntExtra("recordKey", -1);
                    if (recordKey >= 0) {
                        removeProtocolResponse(recordKey);
                    } else {
                        Log.d(TAG, "Bad parameters for REMOVE_OFFLOAD command");
                    }
                    break;
                }
                case "ADD_PASSTHROUGH": {
                    String iface = intent.getStringExtra("iface");
                    String qname = intent.getStringExtra("qname");
                    if (iface != null && qname != null) {
                        registerPassthrough(qname, iface);
                    } else {
                        Log.d(TAG, "Bad parameters for ADD_PASSTHROUGH command");
                    }
                    break;
                }
                case "REMOVE_PASSTHROUGH": {
                    String iface = intent.getStringExtra("iface");
                    String qname = intent.getStringExtra("qname");
                    if (iface != null && qname != null) {
                        removePassthrough(qname, iface);
                    } else {
                        Log.d(TAG, "Bad parameters for REMOVE_PASSTHROUGH command");
                    }
                    break;
                }
            }
        }
    }


    private void bindMdnsOffloadManager() {
        ComponentName componentName = ComponentName.unflattenFromString(
                "com.android.tv.mdnsoffloadmanager/.MdnsOffloadManagerService");
        Intent explicitIntent = new Intent();
        explicitIntent.setComponent(componentName);
        boolean bindingSuccessful = bindService(explicitIntent,
                mMdnsOffloadManagerServiceConnection, Context.BIND_AUTO_CREATE);
        if (!bindingSuccessful) {
            String msg = "Failed to bind MdnsOffloadManager.";
            Log.e(TAG, msg);
            throw new IllegalStateException(msg);
        }
    }


    private final ServiceConnection mMdnsOffloadManagerServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName className, IBinder service) {
            Log.i(TAG, "IMdnsOffloadManager service bound successfully.");
            mMdnsOffloadManagerService = IMdnsOffloadManager.Stub.asInterface(service);
        }

        public void onServiceDisconnected(ComponentName className) {
            Log.e(TAG, "IMdnsOffloadManager service has unexpectedly disconnected.");
            mMdnsOffloadManagerService = null;
        }
    };

}