/*
 * Copyright (c) 2018, Nordic Semiconductor
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package no.nordicsemi.android.ble.common.data.cgm;

import androidx.annotation.FloatRange;
import androidx.annotation.IntRange;

import no.nordicsemi.android.ble.common.profile.cgm.CGMTypes;
import no.nordicsemi.android.ble.common.profile.glucose.GlucoseSampleLocation;
import no.nordicsemi.android.ble.common.profile.glucose.GlucoseSampleType;
import no.nordicsemi.android.ble.common.util.CRC16;
import no.nordicsemi.android.ble.data.Data;
import no.nordicsemi.android.ble.data.MutableData;

@SuppressWarnings("unused")
public final class CGMSpecificOpsControlPointData implements CGMTypes {
	private static final byte OP_CODE_SET_COMMUNICATION_INTERVAL = 1;
	private static final byte OP_CODE_GET_COMMUNICATION_INTERVAL = 2;
	private static final byte OP_CODE_SET_CALIBRATION_VALUE = 4;
	private static final byte OP_CODE_GET_CALIBRATION_VALUE = 5;
	private static final byte OP_CODE_SET_PATIENT_HIGH_ALERT_LEVEL = 7;
	private static final byte OP_CODE_GET_PATIENT_HIGH_ALERT_LEVEL = 8;
	private static final byte OP_CODE_SET_PATIENT_LOW_ALERT_LEVEL = 10;
	private static final byte OP_CODE_GET_PATIENT_LOW_ALERT_LEVEL = 11;
	private static final byte OP_CODE_SET_HYPO_ALERT_LEVEL = 13;
	private static final byte OP_CODE_GET_HYPO_ALERT_LEVEL = 14;
	private static final byte OP_CODE_SET_HYPER_ALERT_LEVEL = 16;
	private static final byte OP_CODE_GET_HYPER_ALERT_LEVEL = 17;
	private static final byte OP_CODE_SET_RATE_OF_DECREASE_ALERT_LEVEL = 19;
	private static final byte OP_CODE_GET_RATE_OF_DECREASE_ALERT_LEVEL = 20;
	private static final byte OP_CODE_SET_RATE_OF_INCREASE_ALERT_LEVEL = 22;
	private static final byte OP_CODE_GET_RATE_OF_INCREASE_ALERT_LEVEL = 23;
	private static final byte OP_CODE_RESET_DEVICE_SPECIFIC_ERROR = 25;
	private static final byte OP_CODE_START_SESSION = 26;
	private static final byte OP_CODE_STOP_SESSION = 27;

	private CGMSpecificOpsControlPointData() {
		// empty private constructor
	}

	public static Data startSession(final boolean secure) {
		return create(OP_CODE_START_SESSION, secure);
	}

	public static Data stopSession(final boolean secure) {
		return create(OP_CODE_STOP_SESSION, secure);
	}

	public static Data resetDeviceSpecificAlert(final boolean secure) {
		return create(OP_CODE_RESET_DEVICE_SPECIFIC_ERROR, secure);
	}

	public static Data setCommunicationInterval(@IntRange(from = 0, to = 65535) final int interval,
												final boolean secure) {
		return create(OP_CODE_SET_COMMUNICATION_INTERVAL, interval, Data.FORMAT_UINT8, secure);
	}

	public static Data setCommunicationIntervalToFastestSupported(final boolean secure) {
		return create(OP_CODE_SET_COMMUNICATION_INTERVAL, 0xFF, Data.FORMAT_UINT8, secure);
	}

	public static Data disablePeriodicCommunication(final boolean secure) {
		return create(OP_CODE_SET_COMMUNICATION_INTERVAL, 0xFF, Data.FORMAT_UINT8, secure);
	}

	public static Data getCommunicationInterval(final boolean secure) {
		return create(OP_CODE_GET_COMMUNICATION_INTERVAL, secure);
	}

	public static Data setCalibrationValue(@FloatRange(from = 0) final float glucoseConcentrationOfCalibration,
										   @GlucoseSampleType final int sampleType,
										   @GlucoseSampleLocation final int sampleLocation,
										   @IntRange(from = 0, to = 65535) final int calibrationTime,
										   @IntRange(from = 0, to = 65535) final int nextCalibrationTime,
										   final boolean secure) {
		final MutableData data = new MutableData(new byte[11 + (secure ? 2 : 0)]);
		data.setByte(OP_CODE_SET_CALIBRATION_VALUE, 0);
		data.setValue(glucoseConcentrationOfCalibration, Data.FORMAT_SFLOAT, 1);
		data.setValue(calibrationTime, Data.FORMAT_UINT16, 3);
		final int typeAndSampleLocation = ((sampleLocation & 0xF) << 8) | (sampleType & 0xF);
		data.setValue(typeAndSampleLocation, Data.FORMAT_UINT8, 5);
		data.setValue(nextCalibrationTime, Data.FORMAT_UINT16, 6);
		data.setValue(0, Data.FORMAT_UINT16, 8); // ignored: calibration data record number
		data.setValue(0, Data.FORMAT_UINT8, 10); // ignored: calibration status
		return appendCrc(data, secure);
	}

	public static Data getCalibrationValue(@IntRange(from = 0) final int calibrationDataRecordNumber, final boolean secure) {
		return create(OP_CODE_GET_CALIBRATION_VALUE, calibrationDataRecordNumber, Data.FORMAT_UINT16, secure);
	}

	public static Data getLastCalibrationValue(final boolean secure) {
		return create(OP_CODE_GET_CALIBRATION_VALUE, 0xFFFF, Data.FORMAT_UINT16, secure);
	}

	public static Data setPatientHighAlertLevel(@FloatRange(from = 0) final float level,
												final boolean secure) {
		return create(OP_CODE_SET_PATIENT_HIGH_ALERT_LEVEL, level, secure);
	}

	public static Data getPatientHighAlertLevel(final boolean secure) {
		return create(OP_CODE_GET_PATIENT_HIGH_ALERT_LEVEL, secure);
	}

	public static Data setPatientLowAlertLevel(@FloatRange(from = 0) final float level,
											   final boolean secure) {
		return create(OP_CODE_SET_PATIENT_LOW_ALERT_LEVEL, level, secure);
	}

	public static Data getPatientLowAlertLevel(final boolean secure) {
		return create(OP_CODE_GET_PATIENT_LOW_ALERT_LEVEL, secure);
	}

	public static Data setHypoAlertLevel(@FloatRange(from = 0) final float level,
										 final boolean secure) {
		return create(OP_CODE_SET_HYPO_ALERT_LEVEL, level, secure);
	}

	public static Data getHypoAlertLevel(final boolean secure) {
		return create(OP_CODE_GET_HYPO_ALERT_LEVEL, secure);
	}

	public static Data setHyperAlertLevel(@FloatRange(from = 0) final float level,
										  final boolean secure) {
		return create(OP_CODE_SET_HYPER_ALERT_LEVEL, level, secure);
	}

	public static Data getHyperAlertLevel(final boolean secure) {
		return create(OP_CODE_GET_HYPER_ALERT_LEVEL, secure);
	}

	public static Data setRateOfDecreaseAlertLevel(@FloatRange(from = 0) final float level,
												   final boolean secure) {
		return create(OP_CODE_SET_RATE_OF_DECREASE_ALERT_LEVEL, level, secure);
	}

	public static Data getRateOfDecreaseAlertLevel(final boolean secure) {
		return create(OP_CODE_GET_RATE_OF_DECREASE_ALERT_LEVEL, secure);
	}

	public static Data setRateOfIncreaseAlertLevel(@FloatRange(from = 0) final float level,
												   final boolean secure) {
		return create(OP_CODE_SET_RATE_OF_INCREASE_ALERT_LEVEL, level, secure);
	}

	public static Data getRateOfIncreaseAlertLevel(final boolean secure) {
		return create(OP_CODE_GET_RATE_OF_INCREASE_ALERT_LEVEL, secure);
	}

	private static Data create(final byte opCode, final boolean secure) {
		final MutableData data = new MutableData(new byte[1 + (secure ? 2 : 0)]);
		data.setByte(opCode, 0);
		return appendCrc(data, secure);
	}

	private static Data create(final byte opCode, final int value, final int format, final boolean secure) {
		final MutableData data = new MutableData(new byte[1 + (format & 0xF) + (secure ? 2 : 0)]);
		data.setByte(opCode, 0);
		data.setValue(value, format, 1);
		return appendCrc(data, secure);
	}

	private static Data create(final byte opCode, final float value, final boolean secure) {
		final MutableData data = new MutableData(new byte[3 + (secure ? 2 : 0)]);
		data.setByte(opCode, 0);
		data.setValue(value, Data.FORMAT_SFLOAT, 1);
		return appendCrc(data, secure);
	}

	@SuppressWarnings("ConstantConditions")
	private static Data appendCrc(final MutableData data, final boolean secure) {
		if (secure) {
			final int length = data.size() - 2;
			final int crc = CRC16.MCRF4XX(data.getValue(), 0, length);
			data.setValue(crc, Data.FORMAT_UINT16, length);
		}
		return data;
	}
}