/*
 * Copyright 2018 cerg2010cerg2010
 *
 * 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 javax.bluetooth;

import android.Manifest;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.content.Intent;
import android.os.Build;

import java.util.Hashtable;

import javax.microedition.io.Connection;
import javax.microedition.util.ActivityResultListener;
import javax.microedition.util.ContextHolder;

public class LocalDevice implements ActivityResultListener {
	private static LocalDevice dev;
	private DiscoveryAgent agent;
	private static Hashtable<String, String> properties;
	private volatile boolean lock = false;
	private boolean cancelled = false;
	private Object monitor = new Object();

	static {
		properties = new Hashtable<>();
		properties.put("bluetooth.api.version", "1.1");
		properties.put("bluetooth.master.switch", "true");
		properties.put("bluetooth.sd.attr.retrievable.max", "256");
		properties.put("bluetooth.connected.devices.max", "7");
		properties.put("bluetooth.l2cap.receiveMTU.max", "672");
		properties.put("bluetooth.sd.trans.max", "1");
		properties.put("bluetooth.connected.inquiry.scan", "true");
		properties.put("bluetooth.connected.page.scan", "true");
		properties.put("bluetooth.connected.inquiry", "true");
		properties.put("bluetooth.connected.page", "true");
	}

	private LocalDevice() throws BluetoothStateException {
		agent = new DiscoveryAgent();
		ContextHolder.addActivityResultListener(this);
		boolean permissionsGranted;
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
			String[] permissions = {
					Manifest.permission.BLUETOOTH_CONNECT,
					Manifest.permission.BLUETOOTH_SCAN,
					Manifest.permission.BLUETOOTH_ADVERTISE,
			};
			permissionsGranted = ContextHolder.requestPermissions(permissions);
		} else {
			permissionsGranted = ContextHolder.requestPermission(Manifest.permission.ACCESS_FINE_LOCATION);
		}
		if (!permissionsGranted) {
			throw new BluetoothStateException();
		}
		if (!DiscoveryAgent.adapter.isEnabled()) {
			Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
			ContextHolder.getActivity().startActivityForResult(enableBtIntent, 2);
			synchronized (monitor) {
				try {
					monitor.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			if (cancelled)
				throw new BluetoothStateException();
			cancelled = false;
		}
	}

	public static LocalDevice getLocalDevice() throws BluetoothStateException {
		if (dev == null)
			dev = new LocalDevice();
		return dev;
	}

	public DiscoveryAgent getDiscoveryAgent() {
		return agent;
	}

	public String getFriendlyName() {
		return DiscoveryAgent.adapter.getName();
	}

	public DeviceClass getDeviceClass() {
		return new DeviceClass();
	}

	public boolean setDiscoverable(int mode) throws BluetoothStateException {
		if ((mode != DiscoveryAgent.GIAC) && (mode != DiscoveryAgent.LIAC) && (mode != DiscoveryAgent.NOT_DISCOVERABLE)
				&& (mode < 0x9E8B00 || mode > 0x9E8B3F)) {
			throw new IllegalArgumentException("Invalid discoverable mode");
		}

		if (lock || mode == DiscoveryAgent.NOT_DISCOVERABLE)
			return true;

		Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
		lock = true;
		ContextHolder.getActivity().startActivityForResult(discoverableIntent, 1);
		return true;
	}

	public void onActivityResult(int requestCode, int resultCode, Intent data) {
		if (requestCode == 1) {
			synchronized (monitor) {
				lock = false;

				monitor.notifyAll();
			}
		} else if (requestCode == 2) {
			synchronized (monitor) {
				if (resultCode != Activity.RESULT_OK)
					cancelled = true;
				monitor.notifyAll();
			}
		}
	}

	public static boolean isPowerOn() {
		return BluetoothAdapter.getDefaultAdapter().isEnabled();
	}

	public int getDiscoverable() {
		int scanMode = DiscoveryAgent.adapter.getScanMode();
		switch (scanMode) {
			case BluetoothAdapter.SCAN_MODE_CONNECTABLE:
				return DiscoveryAgent.LIAC;
			case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE:
				return DiscoveryAgent.GIAC;
			case BluetoothAdapter.SCAN_MODE_NONE:
			default:
				return DiscoveryAgent.NOT_DISCOVERABLE;
		}
	}

	public static String getProperty(String property) {
		return properties.get(property);
	}

	private static String androidToJavaAddress(String addr) {
		return addr.replaceAll(":", "");
	}

	public String getBluetoothAddress() {
		return androidToJavaAddress(DiscoveryAgent.adapter.getAddress());
	}

	public ServiceRecord getRecord(Connection notifier) {
		if (notifier == null) {
			throw new NullPointerException("notifier is null");
		}
		if (!(notifier instanceof org.microemu.cldc.btspp.Connection || notifier instanceof org.microemu.cldc.btl2cap.Connection))
			throw new java.lang.IllegalArgumentException("not a RFCOMM connection");

		if (notifier instanceof org.microemu.cldc.btspp.Connection) {
			org.microemu.cldc.btspp.Connection conn = (org.microemu.cldc.btspp.Connection) notifier;
			if (conn.socket == null)
				// probably calling this for local device, so socket isn't opened
				return new J2MEServiceRecord(null, conn.connUuid, false, false);
			else
				return new J2MEServiceRecord(new RemoteDevice(conn.socket.getRemoteDevice()), conn.connUuid, false, false);
		} else {
			org.microemu.cldc.btl2cap.Connection conn = (org.microemu.cldc.btl2cap.Connection) notifier;
			if (conn.socket == null)
				// probably calling this for local device, so socket isn't opened
				return new J2MEServiceRecord(null, conn.connUuid, false, true);
			else
				return new J2MEServiceRecord(new RemoteDevice(conn.socket.getRemoteDevice()), conn.connUuid, false, true);
		}
	}

	// Not supported on Android due to API limitations
	public void updateRecord(ServiceRecord srvRecord) throws ServiceRegistrationException {
		if (srvRecord == null) {
			throw new NullPointerException("Service Record is null");
		}
	}
}
