/**
 * Copyright (c) 2010-2020 Contributors to the openHAB project
 *
 * See the NOTICE file(s) distributed with this work for additional
 * information.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0
 *
 * SPDX-License-Identifier: EPL-2.0
 */

 /* ***********************************************************
 * This file was automatically generated on 2022-01-03.      *
 *                                                           *
 * openHAB Bindings Version 2.0.0                            *
 *                                                           *
 * If you have a bugfix for this file and want to commit it, *
 * please fix the bug in the generator. You can find a link  *
 * to the generators git repository on tinkerforge.com       *
 *************************************************************/

package org.openhab.binding.tinkerforge.internal.device;

import java.net.URI;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.smarthome.config.core.Configuration;
import org.eclipse.smarthome.config.core.ConfigDescription;
import org.eclipse.smarthome.config.core.ConfigDescriptionBuilder;
import org.eclipse.smarthome.config.core.ConfigDescriptionParameter.Type;
import org.eclipse.smarthome.config.core.ConfigDescriptionParameterBuilder;
import org.eclipse.smarthome.config.core.ConfigDescriptionParameterGroup;
import org.eclipse.smarthome.config.core.ParameterOption;
import org.eclipse.smarthome.core.types.State;
import org.eclipse.smarthome.core.types.StateOption;
import org.eclipse.smarthome.core.types.Command;
import org.eclipse.smarthome.core.types.CommandDescriptionBuilder;
import org.eclipse.smarthome.core.types.CommandOption;
import org.eclipse.smarthome.core.thing.ChannelUID;
import org.eclipse.smarthome.core.thing.ThingStatus;
import org.eclipse.smarthome.core.thing.ThingTypeUID;
import org.eclipse.smarthome.core.thing.binding.BaseThingHandler;
import org.eclipse.smarthome.core.thing.type.ChannelDefinitionBuilder;
import org.eclipse.smarthome.core.thing.type.ChannelType;
import org.eclipse.smarthome.core.thing.type.ChannelTypeBuilder;
import org.eclipse.smarthome.core.thing.type.ChannelTypeUID;
import org.eclipse.smarthome.core.types.RefreshType;
import org.eclipse.smarthome.core.thing.type.ThingType;
import org.eclipse.smarthome.core.thing.type.ThingTypeBuilder;
import org.eclipse.smarthome.core.types.StateDescriptionFragmentBuilder;
import org.openhab.binding.tinkerforge.internal.TinkerforgeBindingConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.eclipse.smarthome.core.library.types.DecimalType;
import org.eclipse.smarthome.core.library.types.QuantityType;
import org.eclipse.smarthome.core.library.unit.MetricPrefix;
import org.eclipse.smarthome.core.library.unit.SIUnits;
import org.eclipse.smarthome.core.library.unit.SmartHomeUnits;
import org.eclipse.smarthome.core.library.types.OnOffType;

import com.tinkerforge.BrickMaster;
import com.tinkerforge.IPConnection;
import com.tinkerforge.TinkerforgeException;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;

/**
 * Wraps the Java Bindings of the Master Brick.
 * @author Erik Fleckstein - Initial contribution
 */
@NonNullByDefault
public class BrickMasterWrapper extends BrickMaster implements DeviceWrapper {
    public static final DeviceInfo DEVICE_INFO = new DeviceInfo(BrickMaster.DEVICE_DISPLAY_NAME, "brickmaster", BrickMaster.DEVICE_IDENTIFIER, BrickMasterWrapper.class, BrickMasterActions.class, "2.4.2", false);

    public BrickMasterWrapper(String uid, IPConnection ipcon) {
        super(uid, ipcon);
    }

    private List<ScheduledFuture<?>> manualChannelUpdates = new ArrayList<>();
    private List<ListenerReg<?>> listenerRegs = new ArrayList<>();

    public void cancelManualUpdates() {
        manualChannelUpdates.forEach(f -> f.cancel(true));
    }

    public <T> T reg(T listener, Consumer<T> toRemove) {
        listenerRegs.add(new ListenerReg<T>(listener, toRemove));
        return listener;
    }

    
    private final Logger logger = LoggerFactory.getLogger(BrickMasterWrapper.class);

    @Override
    public void initialize(org.eclipse.smarthome.config.core.Configuration config, Function<String, org.eclipse.smarthome.config.core.Configuration> getChannelConfigFn, BiConsumer<String, org.eclipse.smarthome.core.types.State> updateStateFn, BiConsumer<String, String> triggerChannelFn, ScheduledExecutorService scheduler, BaseThingHandler handler) throws TinkerforgeException {
        this.setResponseExpectedAll(true);
        BrickMasterConfig cfg = (BrickMasterConfig) config.as(BrickMasterConfig.class);
        this.addStackVoltageListener(this.reg((voltage) -> {if(true) {updateStateFn.accept("BrickMasterStackVoltage", transformBrickMasterStackVoltageCallback0(voltage, cfg));}}, this::removeStackVoltageListener));
		if(Helper.compareFWs(this.getIdentity().hardwareVersion, new short[]{2, 1, 0}) < 0) {
this.addUSBVoltageListener(this.reg((voltage) -> {if(true) {updateStateFn.accept("BrickMasterUSBVoltage", transformBrickMasterUSBVoltageCallback0(voltage, cfg));}}, this::removeUSBVoltageListener));}
		this.addStackCurrentListener(this.reg((current) -> {if(true) {updateStateFn.accept("BrickMasterStackCurrent", transformBrickMasterStackCurrentCallback0(current, cfg));}}, this::removeStackCurrentListener));
        
		if(cfg.statusLEDConfig) {
		                this.enableStatusLED();
		            } else {
		                this.disableStatusLED();
		            }
		this.setSPITFPBaudrateConfig(cfg.spitfpEnableDynamicBaudrate, cfg.spitfpMinimumDynamicBaudrate);
		this.setSPITFPBaudrate('a', cfg.spitfpBaudratePortA);
		            this.setSPITFPBaudrate('b', cfg.spitfpBaudratePortB);
		            this.setSPITFPBaudrate('c', cfg.spitfpBaudratePortC);
		            this.setSPITFPBaudrate('d', cfg.spitfpBaudratePortD);
		{
		BrickMasterStackVoltageConfig channelCfg = getChannelConfigFn.apply("BrickMasterStackVoltage").as(BrickMasterStackVoltageConfig.class);
		this.setStackVoltageCallbackPeriod(channelCfg.updateInterval);
		this.setStackVoltageCallbackThreshold('x', 0, 0);
		}
		if (Helper.compareFWs(this.getIdentity().hardwareVersion, new short[]{2, 1, 0}) < 0) {
		BrickMasterUSBVoltageConfig channelCfg = getChannelConfigFn.apply("BrickMasterUSBVoltage").as(BrickMasterUSBVoltageConfig.class);
		this.setUSBVoltageCallbackPeriod(channelCfg.updateInterval);
		this.setUSBVoltageCallbackThreshold('x', 0, 0);
		}
		{
		BrickMasterStackCurrentConfig channelCfg = getChannelConfigFn.apply("BrickMasterStackCurrent").as(BrickMasterStackCurrentConfig.class);
		this.setStackCurrentCallbackPeriod(channelCfg.updateInterval);
		this.setStackCurrentCallbackThreshold('x', 0, 0);
		}
    }

    @Override
    public void dispose(org.eclipse.smarthome.config.core.Configuration config) throws TinkerforgeException {
        listenerRegs.forEach(ListenerReg::deregister);

        
        this.setStackVoltageCallbackPeriod(0);
		if(Helper.compareFWs(this.getIdentity().hardwareVersion, new short[]{2, 1, 0}) < 0) {
this.setUSBVoltageCallbackPeriod(0);}
		this.setStackCurrentCallbackPeriod(0);
		
    }

    @Override
    public List<String> getEnabledChannels(org.eclipse.smarthome.config.core.Configuration config) throws TinkerforgeException{
        List<String> result = new ArrayList<String>();
        result.add("BrickMasterStackVoltage");
		if (Helper.compareFWs(this.getIdentity().hardwareVersion, new short[]{2, 1, 0}) < 0) {
                result.add("BrickMasterUSBVoltage");
            }
		result.add("BrickMasterStackCurrent");
		if (Helper.compareFWs(this.getIdentity().hardwareVersion, new short[]{3, 0, 0}) >= 0) {
                result.add("BrickMasterBrickletsEnabled");
            }
        return result;
    }

    public static @Nullable ChannelType getChannelType(ChannelTypeUID channelTypeUID) {
        switch(channelTypeUID.getId()) {
            case "BrickMasterStackVoltage":
                return ChannelTypeBuilder.state(new ChannelTypeUID("tinkerforge", "BrickMasterStackVoltage"), "Stack Voltage", "Number:ElectricPotential").withConfigDescriptionURI(URI.create("channel-type:tinkerforge:BrickMasterStackVoltage")).withDescription("The stack voltage in V. The stack voltage is the voltage that is supplied via the stack, i.e. it is given by a Step-Down or Step-Up Power Supply.")
.withStateDescription(StateDescriptionFragmentBuilder.create().withPattern("%.3f %unit%").withReadOnly(true).build().toStateDescription()).build();
            case "BrickMasterUSBVoltage":
                return ChannelTypeBuilder.state(new ChannelTypeUID("tinkerforge", "BrickMasterUSBVoltage"), "USB Voltage", "Number:ElectricPotential").withConfigDescriptionURI(URI.create("channel-type:tinkerforge:BrickMasterUSBVoltage")).withDescription("The USB voltage in V.")
.withStateDescription(StateDescriptionFragmentBuilder.create().withPattern("%.3f %unit%").withReadOnly(true).build().toStateDescription()).build();
            case "BrickMasterStackCurrent":
                return ChannelTypeBuilder.state(new ChannelTypeUID("tinkerforge", "BrickMasterStackCurrent"), "Stack Current", "Number:ElectricCurrent").withConfigDescriptionURI(URI.create("channel-type:tinkerforge:BrickMasterStackCurrent")).withDescription("The stack current in A. The stack current is the current that is drawn via the stack, i.e. it is given by a Step-Down or Step-Up Power Supply.")
.withStateDescription(StateDescriptionFragmentBuilder.create().withPattern("%.3f %unit%").withReadOnly(true).build().toStateDescription()).build();
            case "BrickMasterBrickletsEnabled":
                return ChannelTypeBuilder.state(new ChannelTypeUID("tinkerforge", "BrickMasterBrickletsEnabled"), "Bricklets Enabled", "Switch").withConfigDescriptionURI(URI.create("channel-type:tinkerforge:BrickMasterBrickletsEnabled")).withDescription("Enables/disables all four Bricklets. If you disable the Bricklets the power supply to the Bricklets will be disconnected.").build();
default:
                break;
        }

        return null;
    }

    public static ThingType getThingType(ThingTypeUID thingTypeUID) {
             Map<String, String> thingTypeProperties = ThingTypeBuilder.instance(thingTypeUID, "unused").build().getProperties();
             Map<String, String> props = new HashMap<String, String>(thingTypeProperties);
             props.putIfAbsent(TinkerforgeBindingConstants.PROPERTY_MINIMUM_FIRMWARE_VERSION, DEVICE_INFO.minimumFWVersion);
        return ThingTypeBuilder.instance(thingTypeUID, "Tinkerforge Master Brick").isListed(false).withSupportedBridgeTypeUIDs(Arrays.asList(TinkerforgeBindingConstants.THING_TYPE_BRICK_DAEMON.toString())).withConfigDescriptionURI(URI.create("thing-type:tinkerforge:" + thingTypeUID.getId())).withDescription("Basis to build stacks and has 4 Bricklet ports").withChannelDefinitions(Arrays.asList(new ChannelDefinitionBuilder("BrickMasterStackVoltage", new ChannelTypeUID("tinkerforge", "BrickMasterStackVoltage")).withLabel("Stack Voltage").build(), new ChannelDefinitionBuilder("BrickMasterUSBVoltage", new ChannelTypeUID("tinkerforge", "BrickMasterUSBVoltage")).withLabel("USB Voltage").build(), new ChannelDefinitionBuilder("BrickMasterStackCurrent", new ChannelTypeUID("tinkerforge", "BrickMasterStackCurrent")).withLabel("Stack Current").build(), new ChannelDefinitionBuilder("BrickMasterBrickletsEnabled", new ChannelTypeUID("tinkerforge", "BrickMasterBrickletsEnabled")).withLabel("Bricklets Enabled").build())).withProperties(props).build();
    }

    public static @Nullable ConfigDescription getConfigDescription(URI uri) {
        switch(uri.toASCIIString()) {
            case "thing-type:tinkerforge:brickmaster":
                return ConfigDescriptionBuilder.create(uri).withParameters(Arrays.asList(ConfigDescriptionParameterBuilder.create("statusLEDConfig", Type.BOOLEAN).withDefault("true").withDescription("The status LED is the blue LED next to the USB connector. If enabled is is on and it flickers if data is transfered. If disabled it is always off.").withLabel("Status LED Configuration").build(), ConfigDescriptionParameterBuilder.create("spitfpEnableDynamicBaudrate", Type.BOOLEAN).withDefault("true").withDescription("The SPITF protocol can be used with a dynamic baudrate. If the dynamic baudrate is enabled, the Brick will try to adapt the baudrate for the communication between Bricks and Bricklets according to the amount of data that is transferred.<br/><br/>The baudrate will be increased exponentially if lots of data is sent/received and decreased linearly if little data is sent/received.<br/><br/>This lowers the baudrate in applications where little data is transferred (e.g. a weather station) and increases the robustness. If there is lots of data to transfer (e.g. Thermal Imaging Bricklet) it automatically increases the baudrate as needed.<br/><br/>In cases where some data has to transferred as fast as possible every few seconds (e.g. RS485 Bricklet with a high baudrate but small payload) you may want to turn the dynamic baudrate off to get the highest possible performance.<br/><br/>The maximum value of the baudrate can be set per port. If the dynamic baudrate is disabled, the maximum baudrate will be used statically.").withLabel("SPITFP Enable Dynamic Baudrate").withAdvanced(true).build(), ConfigDescriptionParameterBuilder.create("spitfpMinimumDynamicBaudrate", Type.INTEGER).withDefault("400000").withDescription("See SPITFP Enable Dynamic Baudrate").withLabel("SPITFP Minimum Dynamic Baudrate").withAdvanced(true).withMinimum(BigDecimal.valueOf(400000)).withMaximum(BigDecimal.valueOf(2000000)).build(), ConfigDescriptionParameterBuilder.create("spitfpBaudratePortA", Type.INTEGER).withDefault("1400000").withDescription("The baudrate for Bricklet port A.<br/><br/>If you want to increase the throughput of Bricklets you can increase the baudrate. If you get a high error count because of high interference you can decrease the baudrate.<br/><br/>If the dynamic baudrate feature is enabled, this is the maximum baudrate.<br/><br/>Regulatory testing is done with the default baudrate. If CE compatibility or similar is necessary in your applications we recommend to not change the baudrate.").withLabel("(Maximum) SPITFP Baudrate Port A").withAdvanced(true).withMinimum(BigDecimal.valueOf(400000)).withMaximum(BigDecimal.valueOf(2000000)).build(), ConfigDescriptionParameterBuilder.create("spitfpBaudratePortB", Type.INTEGER).withDefault("1400000").withDescription("The baudrate for Bricklet port B.<br/><br/>If you want to increase the throughput of Bricklets you can increase the baudrate. If you get a high error count because of high interference you can decrease the baudrate.<br/><br/>If the dynamic baudrate feature is enabled, this is the maximum baudrate.<br/><br/>Regulatory testing is done with the default baudrate. If CE compatibility or similar is necessary in your applications we recommend to not change the baudrate.").withLabel("(Maximum) SPITFP Baudrate Port B").withAdvanced(true).withMinimum(BigDecimal.valueOf(400000)).withMaximum(BigDecimal.valueOf(2000000)).build(), ConfigDescriptionParameterBuilder.create("spitfpBaudratePortC", Type.INTEGER).withDefault("1400000").withDescription("The baudrate for Bricklet port C.<br/><br/>If you want to increase the throughput of Bricklets you can increase the baudrate. If you get a high error count because of high interference you can decrease the baudrate.<br/><br/>If the dynamic baudrate feature is enabled, this is the maximum baudrate.<br/><br/>Regulatory testing is done with the default baudrate. If CE compatibility or similar is necessary in your applications we recommend to not change the baudrate.").withLabel("(Maximum) SPITFP Baudrate Port C").withAdvanced(true).withMinimum(BigDecimal.valueOf(400000)).withMaximum(BigDecimal.valueOf(2000000)).build(), ConfigDescriptionParameterBuilder.create("spitfpBaudratePortD", Type.INTEGER).withDefault("1400000").withDescription("The baudrate for Bricklet port D.<br/><br/>If you want to increase the throughput of Bricklets you can increase the baudrate. If you get a high error count because of high interference you can decrease the baudrate.<br/><br/>If the dynamic baudrate feature is enabled, this is the maximum baudrate.<br/><br/>Regulatory testing is done with the default baudrate. If CE compatibility or similar is necessary in your applications we recommend to not change the baudrate.").withLabel("(Maximum) SPITFP Baudrate Port D").withAdvanced(true).withMinimum(BigDecimal.valueOf(400000)).withMaximum(BigDecimal.valueOf(2000000)).build())).withParameterGroups(Arrays.asList(new ConfigDescriptionParameterGroup("update_intervals", "null", true, "Update Intervals", "Update Intervals"))).build();
            case "channel-type:tinkerforge:BrickMasterStackVoltage":
                return ConfigDescriptionBuilder.create(uri).withParameters(Arrays.asList(ConfigDescriptionParameterBuilder.create("updateInterval", Type.INTEGER).withDefault("1000").withDescription("Specifies the update interval in milliseconds. A value of 0 disables automatic updates.").withGroupName("update_intervals").withLabel("Update Interval").withUnit("ms").withMinimum(BigDecimal.valueOf(0)).withMaximum(BigDecimal.valueOf(4294967295L)).build())).withParameterGroups(Arrays.asList()).build();
            case "channel-type:tinkerforge:BrickMasterUSBVoltage":
                return ConfigDescriptionBuilder.create(uri).withParameters(Arrays.asList(ConfigDescriptionParameterBuilder.create("updateInterval", Type.INTEGER).withDefault("1000").withDescription("Specifies the update interval in milliseconds. A value of 0 disables automatic updates.").withGroupName("update_intervals").withLabel("Update Interval").withUnit("ms").withMinimum(BigDecimal.valueOf(0)).withMaximum(BigDecimal.valueOf(4294967295L)).build())).withParameterGroups(Arrays.asList()).build();
            case "channel-type:tinkerforge:BrickMasterStackCurrent":
                return ConfigDescriptionBuilder.create(uri).withParameters(Arrays.asList(ConfigDescriptionParameterBuilder.create("updateInterval", Type.INTEGER).withDefault("1000").withDescription("Specifies the update interval in milliseconds. A value of 0 disables automatic updates.").withGroupName("update_intervals").withLabel("Update Interval").withUnit("ms").withMinimum(BigDecimal.valueOf(0)).withMaximum(BigDecimal.valueOf(4294967295L)).build())).withParameterGroups(Arrays.asList()).build();
            case "channel-type:tinkerforge:BrickMasterBrickletsEnabled":
                return ConfigDescriptionBuilder.create(uri).withParameters(Arrays.asList()).withParameterGroups(Arrays.asList()).build();
            default:
                break;
        }
        return null;
    }

        @Override
    public void refreshValue(String channel, org.eclipse.smarthome.config.core.Configuration config, org.eclipse.smarthome.config.core.Configuration channelConfig, BiConsumer<String, org.eclipse.smarthome.core.types.State> updateStateFn, BiConsumer<String, String> triggerChannelFn) throws TinkerforgeException {
        BrickMasterConfig cfg = (BrickMasterConfig) config.as(BrickMasterConfig.class);
switch(channel) {
            case "BrickMasterStackVoltage":
                   {
            int value = this.getStackVoltage();
            if(true)
                updateStateFn.accept(channel, transformBrickMasterStackVoltageGetter0(value, cfg));
        }
                   return;
            case "BrickMasterUSBVoltage":
                   {
            int value = this.getUSBVoltage();
            if(true)
                updateStateFn.accept(channel, transformBrickMasterUSBVoltageGetter0(value, cfg));
        }
                   return;
            case "BrickMasterStackCurrent":
                   {
            int value = this.getStackCurrent();
            if(true)
                updateStateFn.accept(channel, transformBrickMasterStackCurrentGetter0(value, cfg));
        }
                   return;
            case "BrickMasterBrickletsEnabled":
                   {
            boolean value = this.getBrickletsEnabled();
            if(true)
                updateStateFn.accept(channel, transformBrickMasterBrickletsEnabledGetter0(value, cfg));
        }
                   return;
default:
                logger.warn("Refresh for unknown channel {}", channel);
                break;
        }
    }
    

        @Override
    public List<SetterRefresh> handleCommand(org.eclipse.smarthome.config.core.Configuration config, org.eclipse.smarthome.config.core.Configuration channelConfig, String channel, Command command) throws TinkerforgeException {
        List<SetterRefresh> result = Collections.emptyList();
        
        switch(channel) {
            case "BrickMasterBrickletsEnabled":
                if (command instanceof OnOffType) {
                    OnOffType cmd = (OnOffType) command;
this.setBrickletsEnabled(cmd == OnOffType.ON);
                }
        
                else {
                    logger.warn("Command type {} not supported for channel {}. Please use one of OnOffType.", command.getClass().getName(), channel);
                }
                
                break;
default:
                logger.warn("Command for unknown channel {}", channel);
        }
        return result;
    }

        private org.eclipse.smarthome.core.types.State transformBrickMasterStackVoltageCallback0(int voltage, BrickMasterConfig cfg) {
        return new QuantityType<>(voltage / 1000.0, SmartHomeUnits.VOLT);
    }
	    private org.eclipse.smarthome.core.types.State transformBrickMasterUSBVoltageCallback0(int voltage, BrickMasterConfig cfg) {
        return new QuantityType<>(voltage / 1000.0, SmartHomeUnits.VOLT);
    }
	    private org.eclipse.smarthome.core.types.State transformBrickMasterStackCurrentCallback0(int current, BrickMasterConfig cfg) {
        return new QuantityType<>(current / 1000.0, SmartHomeUnits.AMPERE);
    }
	    private org.eclipse.smarthome.core.types.State transformBrickMasterStackVoltageGetter0(int value, BrickMasterConfig cfg) {
        return new QuantityType<>(value / 1000.0, SmartHomeUnits.VOLT);
    }
	    private org.eclipse.smarthome.core.types.State transformBrickMasterUSBVoltageGetter0(int value, BrickMasterConfig cfg) {
        return new QuantityType<>(value / 1000.0, SmartHomeUnits.VOLT);
    }
	    private org.eclipse.smarthome.core.types.State transformBrickMasterStackCurrentGetter0(int value, BrickMasterConfig cfg) {
        return new QuantityType<>(value / 1000.0, SmartHomeUnits.AMPERE);
    }
	    private org.eclipse.smarthome.core.types.State transformBrickMasterBrickletsEnabledGetter0(boolean value, BrickMasterConfig cfg) {
        return value ? OnOffType.ON : OnOffType.OFF;
    }

}