/*  Copyright (C) 2016-2024 Andreas Shimokawa, Arjan Schrijver, Carsten
    Pfeiffer, Damien Gaignon, Daniel Dakhno, Daniele Gobbetti, Davis Mosenkovs,
    fparri, José Rebelo, mamucho, maxirnilian, mkusnierz, Petr Vaněk, Taavi
    Eomäe

    This file is part of Gadgetbridge.

    Gadgetbridge is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published
    by the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Gadgetbridge is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.adapter;

import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CONNECT;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.Icon;
import android.os.Build;
import android.text.Editable;
import android.text.InputType;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Pair;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.NumberPicker;
import android.widget.PopupMenu;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView;

import com.github.mikephil.charting.charts.PieChart;
import com.github.mikephil.charting.data.PieData;
import com.github.mikephil.charting.data.PieDataSet;
import com.github.mikephil.charting.data.PieEntry;
import com.github.mikephil.charting.utils.MPPointF;
import com.google.android.flexbox.FlexboxLayout;
import com.google.android.material.card.MaterialCardView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.snackbar.Snackbar;
import com.jaredrummler.android.colorpicker.ColorPickerDialog;
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.ActivitySummariesActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.BatteryInfoActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureAlarms;
import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureReminders;
import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2;
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateDialog;
import nodomain.freeyourgadget.gadgetbridge.activities.OpenFwAppInstallerActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.VibrationActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityChartsActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceFolder;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.FormatUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;

/**
 * Adapter for displaying GBDevice instances.
 */
public class GBDeviceAdapterv2 extends ListAdapter<GBDevice, GBDeviceAdapterv2.ViewHolder> {
    private static final Logger LOG = LoggerFactory.getLogger(GBDeviceAdapterv2.class);

    private final Context context;
    private List<GBDevice> deviceList;
    private List<GBDevice> devicesListWithFolders;
    private String expandedDeviceAddress = "";
    private String expandedFolderName = "";
    private ViewGroup parent;
    private HashMap<String, long[]> deviceActivityMap = new HashMap();
    private final StableIdGenerator idGenerator = new StableIdGenerator();

    public GBDeviceAdapterv2(Context context, List<GBDevice> deviceList, HashMap<String,long[]> deviceMap) {
        super(new GBDeviceDiffUtil());
        this.context = context;
        this.deviceList = deviceList;
        rebuildFolders();
        this.deviceActivityMap = deviceMap;
    }

    public void rebuildFolders() {
        this.devicesListWithFolders = enrichDeviceListWithFolder(deviceList);
    }

    @SuppressLint("NotifyDataSetChanged")
    public final void refreshSingleDevice(final GBDevice device) {
        final int i = devicesListWithFolders.indexOf(device);
        if (i > 0) {
            notifyItemChanged(i);
        } else {
            // Somehow the device was not on the list - rebuild everything
            rebuildFolders();
            notifyDataSetChanged();
        }
    }

    private List<GBDevice> enrichDeviceListWithFolder(List<GBDevice> deviceList) {
        final Map<String, List<GBDevice>> devicesPerFolder = new LinkedHashMap<>();
        final List<GBDevice> enrichedList = new ArrayList<>();

        for (GBDevice device : deviceList) {
            String folder = device.getParentFolder();
            if (StringUtils.isNullOrEmpty(folder)){
                enrichedList.add(device);
                continue;
            }
            if (!devicesPerFolder.containsKey(folder)) {
                devicesPerFolder.put(folder, new ArrayList<>());
            }
            devicesPerFolder.get(folder).add(device);
        }

        for (final Map.Entry<String, List<GBDevice>> folder : devicesPerFolder.entrySet()) {
            enrichedList.add(new GBDeviceFolder(folder.getKey()));
            if (folder.getKey().equals(expandedFolderName)) {
                enrichedList.addAll(folder.getValue());
            }
        }

        return enrichedList;
    }

    @NonNull
    @Override
    public GBDeviceAdapterv2.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        this.parent = parent;
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.device_itemv2, parent, false);
        return new ViewHolder(view);
    }

    private int countDevicesInFolder(String folderName, boolean needsToBeConnected){
        int count = 0;
        for(GBDevice device : deviceList){
            if(folderName.equals(device.getParentFolder()) && ((!needsToBeConnected) || device.isConnected())){
                count++;
            }
        }
        return count;
    }

    private void showDeviceFolder(ViewHolder holder, final GBDeviceFolder folder){
        holder.container.setVisibility(View.VISIBLE);
        holder.deviceNameLabel.setText(folder.getName());
        holder.infoIcons.setVisibility(View.GONE);
        holder.deviceInfoBox.setVisibility(View.GONE);
        holder.cardViewActivityCardLayout.setVisibility(View.GONE);
        if(countDevicesInFolder(folder.getName(), true) == 0){
            holder.deviceImageView.setImageResource(R.drawable.ic_device_folder_disabled);
        }else{

            holder.deviceImageView.setImageResource(R.drawable.ic_device_folder);
        }
        holder.deviceInfoView.setVisibility(View.GONE);
        int countInFolder = countDevicesInFolder(folder.getName(), false);
        int connectedInFolder = countDevicesInFolder(folder.getName(), true);
        holder.deviceStatusLabel.setText(context.getString(R.string.controlcenter_connected_fraction, connectedInFolder, countInFolder));

        holder.container.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (expandedFolderName.equals(folder.getName())){
                    // collapse open folder
                    expandedFolderName = "";
                } else {
                    expandedFolderName = folder.getName();
                }
                rebuildFolders();
                notifyDataSetChanged();
            }
        });
        holder.container.setOnLongClickListener(null);
    }

    private void setItemMargin(ViewHolder holder, GBDevice device){
        Resources r = context.getResources();
        int widthDp = 8;
        if(!StringUtils.isNullOrEmpty(device.getParentFolder())){
            widthDp = 16;
        }
        float px = TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP,
                widthDp,
                r.getDisplayMetrics()
        );
        CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) holder.container.getLayoutParams();
        layoutParams.setMarginStart((int) px);
        holder.container.setLayoutParams(layoutParams);

        int alpha = 0;
        if(device instanceof GBDeviceFolder && device.getName().equals(expandedFolderName)){
            alpha = 50;
        }else if(!StringUtils.isNullOrEmpty(device.getParentFolder()) && expandedFolderName.equals(device.getParentFolder())){
            alpha = 50;
        }

        holder.root.setBackgroundColor(Color.argb(alpha, 0, 0, 0));
    }

    void handleDeviceConnect(GBDevice device){
        if(!device.getDeviceCoordinator().isConnectable()){
            device.setState(GBDevice.State.WAITING_FOR_SCAN);
            device.sendDeviceUpdateIntent(GBApplication.getContext(), GBDevice.DeviceUpdateSubject.CONNECTION_STATE);
            return;
        }

        GBApplication.deviceService(device).connect();
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, final int position) {
        final GBDevice device = devicesListWithFolders.get(position);

        setItemMargin(holder, device);

        if(device instanceof GBDeviceFolder){
            showDeviceFolder(holder, (GBDeviceFolder) device);
            return;
        }

        String parentFolder = device.getParentFolder();
        if(!StringUtils.isNullOrEmpty(parentFolder)){
            if(parentFolder.equals(expandedFolderName)){
                holder.container.setVisibility(View.VISIBLE);
            }else{
                holder.container.setVisibility(View.GONE);
            }
        }else{
            holder.container.setVisibility(View.VISIBLE);
        }

        long[] dailyTotals = new long[]{0, 0};
        if (deviceActivityMap.containsKey(device.getAddress())) {
            dailyTotals = deviceActivityMap.get(device.getAddress());
        }

        final DeviceCoordinator coordinator = device.getDeviceCoordinator();
        holder.container.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {

                if (device.isInitialized() || device.isConnected()) {
                    showTransientSnackbar(R.string.controlcenter_snackbar_need_longpress);
                } else {
                    showTransientSnackbar(R.string.controlcenter_snackbar_connecting);
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                        createDynamicShortcut(device);
                    }
                    handleDeviceConnect(device);
                }
            }
        });

        holder.container.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                showDeviceSubmenu(v, device);
                return true;
            }
        });

        holder.deviceImageView.setImageResource(device.getEnabledDisabledIconResource());

        holder.deviceNameLabel.setText(getUniqueDeviceName(device));

        if (device.isBusy()) {
            holder.deviceStatusLabel.setText(device.getBusyTask());
            holder.busyIndicator.setVisibility(View.VISIBLE);
        } else {
            holder.deviceStatusLabel.setText(device.getStateString());
            holder.busyIndicator.setVisibility(View.INVISIBLE);
        }

        //begin of action row
        //battery
        // multiple battery support: at this point we support up to three batteries
        // to support more batteries, the battery UI would need to be extended

        holder.batteryStatusBox0.setVisibility(coordinator.getBatteryCount() > 0 ? View.VISIBLE : View.GONE);
        holder.batteryStatusBox1.setVisibility(coordinator.getBatteryCount() > 1 ? View.VISIBLE : View.GONE);
        holder.batteryStatusBox2.setVisibility(coordinator.getBatteryCount() > 2 ? View.VISIBLE : View.GONE);

        LinearLayout[] batteryStatusBoxes = {holder.batteryStatusBox0, holder.batteryStatusBox1, holder.batteryStatusBox2};
        TextView[] batteryStatusLabels = {holder.batteryStatusLabel0, holder.batteryStatusLabel1, holder.batteryStatusLabel2};
        ImageView[] batteryIcons = {holder.batteryIcon0, holder.batteryIcon1, holder.batteryIcon2};

        for (int batteryIndex = 0; batteryIndex < coordinator.getBatteryCount(); batteryIndex++) {

            int batteryLevel = device.getBatteryLevel(batteryIndex);
            float batteryVoltage = device.getBatteryVoltage(batteryIndex);
            BatteryState batteryState = device.getBatteryState(batteryIndex);
            int batteryIcon = device.getBatteryIcon(batteryIndex);
            int batteryLabel = device.getBatteryLabel(batteryIndex); //unused for now
            batteryIcons[batteryIndex].setImageResource(R.drawable.level_list_battery);

            if (batteryIcon != GBDevice.BATTERY_ICON_DEFAULT){
                batteryIcons[batteryIndex].setImageResource(batteryIcon);
            }

            if (batteryLevel != GBDevice.BATTERY_UNKNOWN) {
                batteryStatusLabels[batteryIndex].setText(device.getBatteryLevel(batteryIndex) + "%");
                if (BatteryState.BATTERY_CHARGING.equals(batteryState) ||
                        BatteryState.BATTERY_CHARGING_FULL.equals(batteryState)) {
                    batteryIcons[batteryIndex].setImageLevel(device.getBatteryLevel(batteryIndex) + 100);
                } else {
                    batteryIcons[batteryIndex].setImageLevel(device.getBatteryLevel(batteryIndex));
                }
            } else if (BatteryState.NO_BATTERY.equals(batteryState) && batteryVoltage != GBDevice.BATTERY_UNKNOWN) {
                batteryStatusLabels[batteryIndex].setText(String.format(Locale.getDefault(), "%.2f", batteryVoltage));
                batteryIcons[batteryIndex].setImageLevel(200);
            } else {
                //should be the "default" status, shown when the device is not connected
                batteryStatusLabels[batteryIndex].setText("");
                batteryIcons[batteryIndex].setImageLevel(300);
            }
            final int finalBatteryIndex = batteryIndex;
            batteryStatusBoxes[batteryIndex].setOnClickListener(new View.OnClickListener() {
                                                               @Override
                                                               public void onClick(View v) {
                                                                   Intent startIntent;
                                                                   startIntent = new Intent(context, BatteryInfoActivity.class);
                                                                   startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
                                                                   startIntent.putExtra(GBDevice.BATTERY_INDEX, finalBatteryIndex);
                                                                   context.startActivity(startIntent);
                                                               }
                                                           }
            );

            // Hide the battery status level, if it has no text
            if (TextUtils.isEmpty(batteryStatusLabels[batteryIndex].getText())) {
                batteryStatusLabels[batteryIndex].setVisibility(View.GONE);
            } else {
                batteryStatusLabels[batteryIndex].setVisibility(View.VISIBLE);
            }
        }
        holder.heartRateStatusBox.setVisibility((device.isInitialized() && coordinator.supportsRealtimeData() && coordinator.supportsManualHeartRateMeasurement(device)) ? View.VISIBLE : View.GONE);
        if (parent.getContext() instanceof ControlCenterv2) {
            ActivitySample sample = ((ControlCenterv2) parent.getContext()).getCurrentHRSample(device);
            if (sample != null) {
                holder.heartRateStatusLabel.setText(String.valueOf(sample.getHeartRate()));
            } else {
                holder.heartRateStatusLabel.setText("");
            }

            // Hide the level, if it has no text
            if (TextUtils.isEmpty(holder.heartRateStatusLabel.getText())) {
                holder.heartRateStatusLabel.setVisibility(View.GONE);
            } else {
                holder.heartRateStatusLabel.setVisibility(View.VISIBLE);
            }
        }

        holder.heartRateStatusBox.setOnClickListener(new View.OnClickListener() {
                                                         @Override
                                                         public void onClick(View v) {
                                                             GBApplication.deviceService(device).onHeartRateTest();
                                                             HeartRateDialog dialog = new HeartRateDialog(device, context);
                                                             dialog.show();
                                                         }
                                                     }
        );

        //device specific settings
        holder.deviceSpecificSettingsView.setVisibility(coordinator.getSupportedDeviceSpecificSettings(device)  != null ? View.VISIBLE : View.GONE);
        holder.deviceSpecificSettingsView.setOnClickListener(new View.OnClickListener()

                                                {
                                                    @Override
                                                    public void onClick(View v) {
                                                        Intent startIntent;
                                                        startIntent = new Intent(context, DeviceSettingsActivity.class);
                                                        startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
                                                        startIntent.putExtra(DeviceSettingsActivity.MENU_ENTRY_POINT, DeviceSettingsActivity.MENU_ENTRY_POINTS.DEVICE_SETTINGS);
                                                        context.startActivity(startIntent);
                                                    }
                                                }
        );

        //fetch activity data
        holder.fetchActivityDataBox.setVisibility((device.isInitialized() && coordinator.supportsActivityDataFetching()) ? View.VISIBLE : View.GONE);
        holder.fetchActivityData.setOnClickListener(new View.OnClickListener()

                                                    {
                                                        @Override
                                                        public void onClick(View v) {
                                                            showTransientSnackbar(R.string.busy_task_fetch_activity_data);
                                                            GBApplication.deviceService(device).onFetchRecordedData(RecordedDataTypes.TYPE_SYNC);
                                                        }
                                                    }
        );


        //take screenshot
        holder.takeScreenshotView.setVisibility((device.isInitialized() && coordinator.supportsScreenshots(device)) ? View.VISIBLE : View.GONE);
        holder.takeScreenshotView.setOnClickListener(new View.OnClickListener()

                                                     {
                                                         @Override
                                                         public void onClick(View v) {
                                                             showTransientSnackbar(R.string.controlcenter_snackbar_requested_screenshot);
                                                             GBApplication.deviceService(device).onScreenshotReq();
                                                         }
                                                     }
        );

        //manage apps
        holder.manageAppsView.setVisibility((device.isInitialized() && coordinator.supportsAppsManagement(device)) ? View.VISIBLE : View.GONE);
        holder.manageAppsView.setOnClickListener(new View.OnClickListener()

                                                 {
                                                     @Override
                                                     public void onClick(View v) {
                                                         DeviceCoordinator coordinator = device.getDeviceCoordinator();
                                                         Class<? extends Activity> appsManagementActivity = coordinator.getAppsManagementActivity();
                                                         if (appsManagementActivity != null) {
                                                             Intent startIntent = new Intent(context, appsManagementActivity);
                                                             startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
                                                             context.startActivity(startIntent);
                                                         }
                                                     }
                                                 }
        );

        //set alarms
        holder.setAlarmsView.setVisibility(coordinator.getAlarmSlotCount(device) > 0 ? View.VISIBLE : View.GONE);
        holder.setAlarmsView.setOnClickListener(new View.OnClickListener()

                                                {
                                                    @Override
                                                    public void onClick(View v) {
                                                        Intent startIntent;
                                                        startIntent = new Intent(context, ConfigureAlarms.class);
                                                        startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
                                                        context.startActivity(startIntent);
                                                    }
                                                }
        );

        //set reminders
        holder.setRemindersView.setVisibility(coordinator.getReminderSlotCount(device) > 0 ? View.VISIBLE : View.GONE);
        holder.setRemindersView.setOnClickListener(new View.OnClickListener()

                                                {
                                                    @Override
                                                    public void onClick(View v) {
                                                        Intent startIntent;
                                                        startIntent = new Intent(context, ConfigureReminders.class);
                                                        startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
                                                        context.startActivity(startIntent);
                                                    }
                                                }
        );

        //show graphs
        holder.showActivityGraphs.setVisibility(coordinator.supportsActivityTracking() ? View.VISIBLE : View.GONE);
        holder.showActivityGraphs.setOnClickListener(new View.OnClickListener()

                                                     {
                                                         @Override
                                                         public void onClick(View v) {
                                                             Intent startIntent;
                                                             startIntent = new Intent(context, ActivityChartsActivity.class);
                                                             startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
                                                             context.startActivity(startIntent);
                                                         }
                                                     }
        );

        //show activity tracks
        holder.showActivityTracks.setVisibility(coordinator.supportsActivityTracks() ? View.VISIBLE : View.GONE);
        holder.showActivityTracks.setOnClickListener(new View.OnClickListener()
                                                     {
                                                         @Override
                                                         public void onClick(View v) {
                                                             Intent startIntent;
                                                             startIntent = new Intent(context, ActivitySummariesActivity.class);
                                                             startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
                                                             context.startActivity(startIntent);
                                                         }
                                                     }
        );

        ItemWithDetailsAdapter infoAdapter = new ItemWithDetailsAdapter(context, device.getDeviceInfos());
        infoAdapter.setHorizontalAlignment(true);
        holder.deviceInfoList.setAdapter(infoAdapter);
        justifyListViewHeightBasedOnChildren(holder.deviceInfoList);
        holder.deviceInfoList.setFocusable(false);

        holder.infoIcons.setVisibility(View.VISIBLE);

        final boolean detailsShown = expandedDeviceAddress.equals(device.getAddress());
        boolean showInfoIcon = device.hasDeviceInfos() && !device.isBusy();
        holder.deviceInfoBox.setActivated(detailsShown);
        holder.deviceInfoBox.setVisibility(detailsShown ? View.VISIBLE : View.GONE);
        holder.deviceInfoView.setVisibility(View.VISIBLE);
        holder.deviceInfoView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                showDeviceSubmenu(v, device);
            }
        });

        holder.findDevice.setVisibility(device.isInitialized() && coordinator.supportsFindDevice() ? View.VISIBLE : View.GONE);
        holder.findDevice.setOnClickListener(new View.OnClickListener() {
                                                 @Override
                                                 public void onClick(View v) {
                                                     new MaterialAlertDialogBuilder(context)
                                                             .setCancelable(true)
                                                             .setTitle(context.getString(R.string.controlcenter_find_device))
                                                             .setMessage(context.getString(R.string.find_lost_device_message, device.getName()))
                                                             .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
                                                                 @Override
                                                                 public void onClick(DialogInterface dialog, int which) {
                                                                     if (device.getType() == DeviceType.VIBRATISSIMO) {
                                                                         Intent startIntent;
                                                                         startIntent = new Intent(context, VibrationActivity.class);
                                                                         startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
                                                                         context.startActivity(startIntent);
                                                                         return;
                                                                     }
                                                                     GBApplication.deviceService(device).onFindDevice(true);
                                                                     Snackbar.make(parent, R.string.control_center_find_lost_device, Snackbar.LENGTH_INDEFINITE).setAction(R.string.find_lost_device_you_found_it, new View.OnClickListener() {
                                                                         @Override
                                                                         public void onClick(View v) {
                                                                             GBApplication.deviceService(device).onFindDevice(false);
                                                                         }
                                                                     }).setCallback(new Snackbar.Callback() {
                                                                         @Override
                                                                         public void onDismissed(Snackbar snackbar, int event) {
                                                                             GBApplication.deviceService(device).onFindDevice(false);
                                                                             super.onDismissed(snackbar, event);
                                                                         }
                                                                     }).show();

                                                                 }
                                                             })
                                                             .setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
                                                                 @Override
                                                                 public void onClick(DialogInterface dialog, int which) {
                                                                     // do nothing
                                                                 }
                                                             })
                                                             .show();
//                                                             ProgressDialog.show(
//                                                             context,
//                                                             context.getString(R.string.control_center_find_lost_device),
//                                                             context.getString(R.string.control_center_cancel_to_stop_vibration),
//                                                             true, true,
//                                                             new DialogInterface.OnCancelListener() {
//                                                                 @Override
//                                                                 public void onCancel(DialogInterface dialog) {
//                                                                     GBApplication.deviceService().onFindDevice(false);
//                                                                 }
//                                                             });
                                                 }
                                             }
        );

        holder.calibrateDevice.setVisibility(device.isInitialized() && (coordinator.getCalibrationActivity() != null) ? View.VISIBLE : View.GONE);
        holder.calibrateDevice.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent startIntent = new Intent(context, coordinator.getCalibrationActivity());
                startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
                context.startActivity(startIntent);
            }
        });

        holder.fmFrequencyBox.setVisibility(View.GONE);
        if (device.isInitialized() && device.getExtraInfo("fm_frequency") != null) {
            holder.fmFrequencyBox.setVisibility(View.VISIBLE);
            holder.fmFrequencyLabel.setText(String.format(Locale.getDefault(), "%.1f", (float) device.getExtraInfo("fm_frequency")));
        }
        final TextView fmFrequencyLabel = holder.fmFrequencyLabel;
        final float FREQ_MIN = 87.5F;
        final float FREQ_MAX = 108.0F;
        final int FREQ_MIN_INT = (int) Math.floor(FREQ_MIN);
        final int FREQ_MAX_INT = (int) Math.round(FREQ_MAX);
        final AlertDialog alert[] = new AlertDialog[1];

        holder.fmFrequencyBox.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View view) {
                final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context);
                final LayoutInflater inflater = LayoutInflater.from(context);
                final View frequency_picker_view = inflater.inflate(R.layout.dialog_frequency_picker, null);
                builder.setTitle(R.string.preferences_fm_frequency);
                final float[] fm_presets = new float[3];

                fm_presets[0] = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()).getFloat("fm_preset0", 99);
                fm_presets[1] = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()).getFloat("fm_preset1", 100);
                fm_presets[2] = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()).getFloat("fm_preset2", 101);

                final NumberPicker frequency_decimal_picker = frequency_picker_view.findViewById(R.id.frequency_dec);
                frequency_decimal_picker.setMinValue(FREQ_MIN_INT);
                frequency_decimal_picker.setMaxValue(FREQ_MAX_INT);

                final NumberPicker frequency_fraction_picker = frequency_picker_view.findViewById(R.id.frequency_fraction);
                frequency_fraction_picker.setMinValue(0);
                frequency_fraction_picker.setMaxValue(9);

                final NumberPicker.OnValueChangeListener picker_listener = new NumberPicker.OnValueChangeListener() {
                    @Override
                    public void onValueChange(NumberPicker numberPicker, int oldVal, int newVal) {

                        int decimal_value = numberPicker.getValue();
                        if (decimal_value == FREQ_MIN_INT) {
                            frequency_fraction_picker.setMinValue(5);
                            frequency_fraction_picker.setMaxValue(9);
                        } else if (decimal_value == FREQ_MAX_INT) {
                            frequency_fraction_picker.setMinValue(0);
                            frequency_fraction_picker.setMaxValue(0);
                        } else {
                            frequency_fraction_picker.setMinValue(0);
                            frequency_fraction_picker.setMaxValue(9);
                        }
                    }
                };

                frequency_decimal_picker.setOnValueChangedListener(picker_listener);

                final Button[] button_presets = new Button[]{
                        frequency_picker_view.findViewById(R.id.frequency_preset1),
                        frequency_picker_view.findViewById(R.id.frequency_preset2),
                        frequency_picker_view.findViewById(R.id.frequency_preset3)
                };

                for (int i = 0; i < button_presets.length; i++) {
                    final int index = i;
                    button_presets[index].setText(String.valueOf(fm_presets[index]));
                    button_presets[index].setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View view) {
                            final float frequency = fm_presets[index];
                            device.setExtraInfo("fm_frequency", fm_presets[index]);
                            fmFrequencyLabel.setText(String.format(Locale.getDefault(), "%.1f", (float) frequency));
                            GBApplication.deviceService(device).onSetFmFrequency(frequency);
                            alert[0].dismiss();
                        }
                    });
                    button_presets[index].setOnLongClickListener(new View.OnLongClickListener() {
                        @Override
                        public boolean onLongClick(View view) {
                            final float frequency = (float) (frequency_decimal_picker.getValue() + (0.1 * frequency_fraction_picker.getValue()));
                            fm_presets[index] = frequency;
                            button_presets[index].setText(String.valueOf(frequency));
                            SharedPreferences.Editor editor = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()).edit();
                            editor.putFloat((String.format("fm_preset%s", index)), frequency);
                            editor.apply();
                            return true;
                        }
                    });

                }

                final float frequency = (float) device.getExtraInfo("fm_frequency");
                final int decimal = (int) frequency;
                final int fraction = Math.round((frequency - decimal) * 10);
                frequency_decimal_picker.setValue(decimal);
                picker_listener.onValueChange(frequency_decimal_picker, frequency_decimal_picker.getValue(), decimal);
                frequency_fraction_picker.setValue(fraction);

                builder.setView(frequency_picker_view);

                builder.setPositiveButton(context.getResources().getString(android.R.string.ok),
                        new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                float frequency = (float) (frequency_decimal_picker.getValue() + (0.1 * frequency_fraction_picker.getValue()));
                                if (frequency < FREQ_MIN || frequency > FREQ_MAX) {
                                    new MaterialAlertDialogBuilder(context)
                                            .setTitle(R.string.pref_invalid_frequency_title)
                                            .setMessage(R.string.pref_invalid_frequency_message)
                                            .setNeutralButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                                                public void onClick(DialogInterface dialog, int which) {
                                                }
                                            })
                                            .show();
                                } else {
                                    device.setExtraInfo("fm_frequency", frequency);
                                    fmFrequencyLabel.setText(String.format(Locale.getDefault(), "%.1f", frequency));
                                    GBApplication.deviceService(device).onSetFmFrequency(frequency);
                                }
                            }
                        });
                builder.setNegativeButton(context.getResources().getString(R.string.Cancel), new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.cancel();
                    }
                });

                alert[0] = builder.create();
                alert[0].show();
            }
        });

        holder.ledColor.setVisibility(View.GONE);
        if (device.isInitialized() && device.getExtraInfo("led_color") != null && coordinator.supportsLedColor()) {
            holder.ledColor.setVisibility(View.VISIBLE);
            final GradientDrawable ledColor = (GradientDrawable) holder.ledColor.getDrawable().mutate();
            ledColor.setColor((int) device.getExtraInfo("led_color"));
            holder.ledColor.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    ColorPickerDialog.Builder builder = ColorPickerDialog.newBuilder();
                    builder.setDialogTitle(R.string.preferences_led_color);

                    int[] presets = coordinator.getColorPresets();

                    builder.setColor((int) device.getExtraInfo("led_color"));
                    builder.setShowAlphaSlider(false);
                    builder.setShowColorShades(false);
                    if (coordinator.supportsRgbLedColor()) {
                        builder.setAllowCustom(true);
                        if (presets.length == 0) {
                            builder.setDialogType(ColorPickerDialog.TYPE_CUSTOM);
                        }
                    } else {
                        builder.setAllowCustom(false);
                    }

                    if (presets.length > 0) {
                        builder.setAllowPresets(true);
                        builder.setPresets(presets);
                    }

                    ColorPickerDialog dialog = builder.create();
                    dialog.setColorPickerDialogListener(new ColorPickerDialogListener() {
                        @Override
                        public void onColorSelected(int dialogId, int color) {
                            ledColor.setColor(color);
                            device.setExtraInfo("led_color", color);
                            GBApplication.deviceService(device).onSetLedColor(color);
                        }

                        @Override
                        public void onDialogDismissed(int dialogId) {
                            // Nothing to do
                        }
                    });
                    dialog.show(((AppCompatActivity) context).getSupportFragmentManager(), "color-picker-dialog");
                }
            });
        }

        holder.powerOff.setVisibility(View.GONE);
        if (device.isInitialized() && coordinator.supportsPowerOff()) {
            holder.powerOff.setVisibility(View.VISIBLE);
            holder.powerOff.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    new MaterialAlertDialogBuilder(context)
                            .setTitle(R.string.controlcenter_power_off_confirm_title)
                            .setMessage(R.string.controlcenter_power_off_confirm_description)
                            .setIcon(R.drawable.ic_power_settings_new)
                            .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
                                public void onClick(final DialogInterface dialog, final int whichButton) {
                                    GBApplication.deviceService(device).onPowerOff();
                                }
                            })
                            .setNegativeButton(android.R.string.no, null)
                            .show();
                }
            });
        }

        holder.cardViewActivityCardLayout.setVisibility(coordinator.supportsActivityTracking() ? View.VISIBLE : View.GONE);
        holder.cardViewActivityCardLayout.setMinimumWidth(coordinator.supportsActivityTracking() ? View.VISIBLE : View.GONE);

        if (coordinator.supportsActivityTracking()) {
            setActivityCard(holder, device, dailyTotals);
        }
    }

    private boolean showInstallerItem(GBDevice device) {
        final DeviceCoordinator coordinator = device.getDeviceCoordinator();
        return coordinator.supportsAppsManagement(device) || coordinator.supportsFlashing();
    }

    private void showDeviceSubmenu(final View v, final GBDevice device) {
        boolean deviceConnected = device.getState() != GBDevice.State.NOT_CONNECTED;
        PopupMenu menu = new PopupMenu(v.getContext(), v);
        menu.inflate(R.menu.fragment_devices_device_submenu);

        final boolean detailsShown = expandedDeviceAddress.equals(device.getAddress());
        boolean showInfoIcon = device.hasDeviceInfos() && !device.isBusy();

        menu.getMenu().findItem(R.id.controlcenter_device_submenu_connect).setVisible(!deviceConnected);
        menu.getMenu().findItem(R.id.controlcenter_device_submenu_disconnect).setVisible(deviceConnected);
        menu.getMenu().findItem(R.id.controlcenter_device_submenu_show_details).setEnabled(showInfoIcon);
        menu.getMenu().findItem(R.id.controlcenter_device_submenu_installer).setEnabled(deviceConnected);
        menu.getMenu().findItem(R.id.controlcenter_device_submenu_installer).setVisible(showInstallerItem(device));

        menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(final MenuItem item) {
                final int itemId = item.getItemId();
                if (itemId == R.id.controlcenter_device_submenu_connect) {
                    if (device.getState() != GBDevice.State.CONNECTED) {
                        showTransientSnackbar(R.string.controlcenter_snackbar_connecting);
                        handleDeviceConnect(device);
                    }
                    return true;
                } else if (itemId == R.id.controlcenter_device_submenu_disconnect) {
                    if (device.getState() != GBDevice.State.NOT_CONNECTED) {
                        showTransientSnackbar(R.string.controlcenter_snackbar_disconnecting);
                        GBApplication.deviceService(device).disconnect();
                    }
                    removeFromLastDeviceAddressesPref(device);
                    return true;
                } else if (itemId == R.id.controlcenter_device_submenu_set_alias) {
                    showSetAliasDialog(device);
                    return true;
                } else if (itemId == R.id.controlcenter_device_submenu_remove) {
                    showRemoveDeviceDialog(device);
                    return true;
                } else if (itemId == R.id.controlcenter_device_submenu_show_details) {
                    final String previouslyExpandedDeviceAddress = expandedDeviceAddress;
                    expandedDeviceAddress = detailsShown ? "" : device.getAddress();

                    if (!previouslyExpandedDeviceAddress.isEmpty()) {
                        // Notify the previously expanded device for a change (collapsing it)
                        for (int i = 0; i < devicesListWithFolders.size(); i++) {
                            final GBDevice gbDevice = devicesListWithFolders.get(i);
                            if (gbDevice.getAddress().equals(previouslyExpandedDeviceAddress)) {
                                notifyItemChanged(devicesListWithFolders.indexOf(gbDevice));
                                break;
                            }
                        }
                    }

                    // Update the current one
                    notifyItemChanged(devicesListWithFolders.indexOf(device));
                    return true;
                } else if (itemId == R.id.controlcenter_device_submenu_set_parent_folder) {
                    showSetParentFolderDialog(device);
                    return true;
                } else if (itemId == R.id.controlcenter_device_submenu_installer) {
                    Intent openFwIntent = new Intent(context, OpenFwAppInstallerActivity.class);
                    openFwIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
                    context.startActivity(openFwIntent);
                    return false;
                }

                return false;
            }
        });
        menu.show();
    }

    private void showRemoveDeviceDialog(final GBDevice device) {
        new MaterialAlertDialogBuilder(context)
                .setCancelable(true)
                .setTitle(context.getString(R.string.controlcenter_delete_device_name, device.getName()))
                .setMessage(R.string.controlcenter_delete_device_dialogmessage)
                .setPositiveButton(R.string.Delete, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        try {
                            DeviceCoordinator coordinator = device.getDeviceCoordinator();
                            coordinator.deleteDevice(device);
                            DeviceHelper.getInstance().removeBond(device);
                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                                removeDynamicShortcut(device);
                            }
                        } catch (Exception ex) {
                            GB.toast(context, context.getString(R.string.error_deleting_device, ex.getMessage()), Toast.LENGTH_LONG, GB.ERROR, ex);
                        } finally {
                            Intent refreshIntent = new Intent(DeviceManager.ACTION_REFRESH_DEVICELIST);
                            LocalBroadcastManager.getInstance(context).sendBroadcast(refreshIntent);
                        }
                    }
                })
                .setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        // do nothing
                    }
                })
                .show();
    }

    private void showSetParentFolderDialog(final GBDevice device) {
        final String[] selectedFolder = new String[1];

        final LinearLayout linearLayout = new LinearLayout(context);
        linearLayout.setOrientation(LinearLayout.VERTICAL);

        final LinearLayout newFolderLayout = new LinearLayout(context);
        newFolderLayout.setOrientation(LinearLayout.HORIZONTAL);
        newFolderLayout.setPadding(context.getResources().getDimensionPixelSize(R.dimen.dialog_margin),
                0, context.getResources().getDimensionPixelSize(R.dimen.dialog_margin), 0);

        final TextView newFolderLabel = new TextView(context);
        newFolderLabel.setText(R.string.controlcenter_folder_name);
        final EditText newFolderInput = new EditText(context);
        newFolderInput.setInputType(InputType.TYPE_CLASS_TEXT);

        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.WRAP_CONTENT);
        newFolderInput.setLayoutParams(params);

        newFolderInput.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void afterTextChanged(Editable editable) {
                selectedFolder[0] = editable.toString();
            }
        });

        newFolderLayout.addView(newFolderLabel);
        newFolderLayout.addView(newFolderInput);

        final Spinner deviceListSpinner = new Spinner(context);
        ArrayList<SpinnerWithIconItem> foldersList = new ArrayList<>();
        for (GBDevice oneDevice : deviceList) {
            String folder = oneDevice.getParentFolder();
            if (StringUtils.isNullOrEmpty(folder)) {
                continue;
            }
            if (folderListContainsName(foldersList, folder)) {
                continue;
            }
            foldersList.add(new SpinnerWithIconItem(folder, 2L, R.drawable.ic_folder));
        }

        foldersList.add(new SpinnerWithIconItem(context.getString(R.string.controlcenter_add_new_folder), 0L, R.drawable.ic_create_new_folder));
        if (foldersList.toArray().length > 1) {
            foldersList.add(new SpinnerWithIconItem(context.getString(R.string.controlcenter_unset_folder), 1L, R.drawable.ic_folder_delete));
        }

        final SpinnerWithIconAdapter deviceListAdapter = new SpinnerWithIconAdapter((Activity) context,
                R.layout.spinner_with_image_layout, R.id.spinner_item_text, foldersList);
        deviceListSpinner.setAdapter(deviceListAdapter);

        deviceListSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
                SpinnerWithIconItem selectedItem = (SpinnerWithIconItem) parent.getItemAtPosition(pos);
                int folderId = selectedItem.getId().intValue();
                switch (folderId) {
                    case 0: //Add new folder from test input
                        newFolderLayout.setVisibility(View.VISIBLE);
                        selectedFolder[0] = newFolderInput.getText().toString();
                        break;
                    case 1: //Unset folder
                        newFolderLayout.setVisibility(View.GONE);
                        selectedFolder[0] = "";
                        break;
                    default: //Set folder from selection
                        newFolderLayout.setVisibility(View.GONE);
                        selectedFolder[0] = selectedItem.getText();
                        break;
                }
            }

            @Override
            public void onNothingSelected(AdapterView<?> arg0) {
                // TODO Auto-generated method stub
            }
        });

        linearLayout.addView(deviceListSpinner);
        linearLayout.addView(newFolderLayout);

        new MaterialAlertDialogBuilder(context)
                .setCancelable(true)
                .setTitle(R.string.controlcenter_set_folder_title)
                .setView(linearLayout)
                .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {

                        try (DBHandler dbHandler = GBApplication.acquireDB()) {
                            DaoSession session = dbHandler.getDaoSession();
                            Device dbDevice = DBHelper.getDevice(device, session);
                            String parentFolder = selectedFolder[0];
                            dbDevice.setParentFolder(parentFolder);
                            dbDevice.update();
                            device.setParentFolder(parentFolder);
                            expandedFolderName = parentFolder;
                        } catch (Exception ex) {
                            GB.toast(context, context.getString(R.string.error_setting_parent_folder, ex.getMessage()), Toast.LENGTH_LONG, GB.ERROR, ex);
                        } finally {
                            Intent refreshIntent = new Intent(DeviceManager.ACTION_REFRESH_DEVICELIST);
                            LocalBroadcastManager.getInstance(context).sendBroadcast(refreshIntent);
                        }
                    }
                })
                .setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                    }
                })
                .show();
    }

    private boolean folderListContainsName(ArrayList<SpinnerWithIconItem> list, String name){
        for (SpinnerWithIconItem item: list) {
            if (item.getText().equals(name)){
                return true;
            }
        }
        return false;
    }

    private void removeFromLastDeviceAddressesPref(GBDevice device) {
        Set<String> lastDeviceAddresses = GBApplication.getPrefs().getStringSet(GBPrefs.LAST_DEVICE_ADDRESSES, Collections.emptySet());
        if (lastDeviceAddresses.contains(device.getAddress())) {
            lastDeviceAddresses = new HashSet<String>(lastDeviceAddresses);
            lastDeviceAddresses.remove(device.getAddress());
            GBApplication.getPrefs().getPreferences().edit().putStringSet(GBPrefs.LAST_DEVICE_ADDRESSES, lastDeviceAddresses).apply();
        }
    }

    private void setAppPreferences(GBDevice device) {
        Intent startIntent;
        startIntent = new Intent(context, DeviceSettingsActivity.class);
        startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
        startIntent.putExtra(DeviceSettingsActivity.MENU_ENTRY_POINT, DeviceSettingsActivity.MENU_ENTRY_POINTS.APPLICATION_SETTINGS);
        context.startActivity(startIntent);
    }
    private void showSetAliasDialog(final GBDevice device) {
        final EditText input = new EditText(context);
        input.setInputType(InputType.TYPE_CLASS_TEXT);
        input.setText(device.getAlias());
        FrameLayout container = new FrameLayout(context);
        FrameLayout.LayoutParams params = new  FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        params.leftMargin = context.getResources().getDimensionPixelSize(R.dimen.dialog_margin);
        params.rightMargin = context.getResources().getDimensionPixelSize(R.dimen.dialog_margin);
        input.setLayoutParams(params);
        container.addView(input);
        // Specify the type of input expected; this, for example, sets the input as a password, and will mask the text

        new MaterialAlertDialogBuilder(context)
                .setView(container)
                .setCancelable(true)
                .setTitle(context.getString(R.string.controlcenter_set_alias))
                .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        try (DBHandler dbHandler = GBApplication.acquireDB()) {
                            DaoSession session = dbHandler.getDaoSession();
                            Device dbDevice = DBHelper.getDevice(device, session);
                            String alias = input.getText().toString();
                            dbDevice.setAlias(alias);
                            dbDevice.update();
                            device.setAlias(alias);
                        } catch (Exception ex) {
                            GB.toast(context, context.getString(R.string.error_setting_alias) + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex);
                        } finally {
                            Intent refreshIntent = new Intent(DeviceManager.ACTION_REFRESH_DEVICELIST);
                            LocalBroadcastManager.getInstance(context).sendBroadcast(refreshIntent);
                        }
                    }
                })
                .setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        // do nothing
                    }
                })
                .show();
    }

    @Override
    public int getItemCount() {
        return devicesListWithFolders.size();
    }

    @Override
    public long getItemId(int position) {
        return idGenerator.getId(devicesListWithFolders.get(position));
    }

    static class ViewHolder extends RecyclerView.ViewHolder {

        View root;
        MaterialCardView container;

        ImageView deviceImageView;
        TextView deviceNameLabel;
        TextView deviceStatusLabel;

        //actions
        LinearLayout batteryStatusBox0;
        TextView batteryStatusLabel0;
        ImageView batteryIcon0;
        LinearLayout batteryStatusBox1;
        TextView batteryStatusLabel1;
        ImageView batteryIcon1;
        LinearLayout batteryStatusBox2;
        TextView batteryStatusLabel2;
        ImageView batteryIcon2;
        ImageView deviceSpecificSettingsView;
        LinearLayout fetchActivityDataBox;
        ImageView fetchActivityData;
        ProgressBar busyIndicator;
        ImageView takeScreenshotView;
        ImageView manageAppsView;
        ImageView setAlarmsView;
        ImageView setRemindersView;
        ImageView showActivityGraphs;
        ImageView showActivityTracks;
        ImageView calibrateDevice;
        LinearLayout heartRateStatusBox;
        ImageView heartRateIcon;
        TextView heartRateStatusLabel;
        FlexboxLayout infoIcons;


        ImageView deviceInfoView;
        //overflow
        final RelativeLayout deviceInfoBox;
        ListView deviceInfoList;
        ImageView findDevice;
        LinearLayout fmFrequencyBox;
        TextView fmFrequencyLabel;
        ImageView ledColor;
        ImageView powerOff;

        //activity card
        LinearLayout cardViewActivityCardLayout;
        PieChart TotalStepsChart;
        PieChart TotalDistanceChart;
        PieChart SleepTimeChart;

        ViewHolder(View view) {
            super(view);

            root = view;

            container = view.findViewById(R.id.card_view);

            deviceImageView = view.findViewById(R.id.device_image);
            deviceNameLabel = view.findViewById(R.id.device_name);
            deviceStatusLabel = view.findViewById(R.id.device_status);

            //actions
            batteryStatusBox0 = view.findViewById(R.id.device_battery_status_box);
            batteryStatusLabel0 = view.findViewById(R.id.battery_status);
            batteryIcon0 = view.findViewById(R.id.device_battery_status);
            batteryStatusBox1 = view.findViewById(R.id.device_battery_status_box1);
            batteryStatusLabel1 = view.findViewById(R.id.battery_status1);
            batteryIcon1 = view.findViewById(R.id.device_battery_status1);
            batteryStatusBox2 = view.findViewById(R.id.device_battery_status_box2);
            batteryStatusLabel2 = view.findViewById(R.id.battery_status2);
            batteryIcon2 = view.findViewById(R.id.device_battery_status2);



            deviceSpecificSettingsView = view.findViewById(R.id.device_specific_settings);
            fetchActivityDataBox = view.findViewById(R.id.device_action_fetch_activity_box);
            fetchActivityData = view.findViewById(R.id.device_action_fetch_activity);
            busyIndicator = view.findViewById(R.id.device_busy_indicator);
            takeScreenshotView = view.findViewById(R.id.device_action_take_screenshot);
            manageAppsView = view.findViewById(R.id.device_action_manage_apps);
            setAlarmsView = view.findViewById(R.id.device_action_set_alarms);
            setRemindersView = view.findViewById(R.id.device_action_set_reminders);
            showActivityGraphs = view.findViewById(R.id.device_action_show_activity_graphs);
            showActivityTracks = view.findViewById(R.id.device_action_show_activity_tracks);
            deviceInfoView = view.findViewById(R.id.device_info_image);
            calibrateDevice = view.findViewById(R.id.device_action_calibrate);

            deviceInfoBox = view.findViewById(R.id.device_item_infos_box);
            //overflow
            deviceInfoList = view.findViewById(R.id.device_item_infos);
            findDevice = view.findViewById(R.id.device_action_find);
            fmFrequencyBox = view.findViewById(R.id.device_fm_frequency_box);
            fmFrequencyLabel = view.findViewById(R.id.fm_frequency);
            ledColor = view.findViewById(R.id.device_led_color);
            powerOff = view.findViewById(R.id.device_action_power_off);
            heartRateStatusBox = view.findViewById(R.id.device_heart_rate_status_box);
            heartRateStatusLabel = view.findViewById(R.id.heart_rate_status);
            heartRateIcon = view.findViewById(R.id.device_heart_rate_status);
            infoIcons = view.findViewById(R.id.device_info_icons);

            cardViewActivityCardLayout = view.findViewById(R.id.card_view_activity_card_layout);

            TotalStepsChart = view.findViewById(R.id.activity_dashboard_piechart1);
            TotalDistanceChart = view.findViewById(R.id.activity_dashboard_piechart2);
            SleepTimeChart = view.findViewById(R.id.activity_dashboard_piechart3);
        }

    }

    private void justifyListViewHeightBasedOnChildren(ListView listView) {
        ArrayAdapter adapter = (ArrayAdapter) listView.getAdapter();

        if (adapter == null) {
            return;
        }
        int totalHeight = 0;
        for (int i = 0; i < adapter.getCount(); i++) {
            View listItem = adapter.getView(i, null, listView);
            listItem.measure(0, 0);
            totalHeight += listItem.getMeasuredHeight();
        }

        ViewGroup.LayoutParams par = listView.getLayoutParams();
        par.height = totalHeight + (listView.getDividerHeight() * (adapter.getCount() - 1));
        listView.setLayoutParams(par);
        listView.requestLayout();
    }

    private String getUniqueDeviceName(GBDevice device) {
        String deviceName = device.getAliasOrName();

        if (!isUniqueDeviceName(device, deviceName)) {
            if (device.getModel() != null) {
                deviceName = deviceName + " " + device.getModel();
                if (!isUniqueDeviceName(device, deviceName)) {
                    deviceName = deviceName + " " + device.getShortAddress();
                }
            } else {
                deviceName = deviceName + " " + device.getShortAddress();
            }
        }
        return deviceName;
    }

    private boolean isUniqueDeviceName(GBDevice device, String deviceName) {
        for (int i = 0; i < deviceList.size(); i++) {
            GBDevice item = deviceList.get(i);
            if (item == device) {
                continue;
            }
            if (deviceName.equals(item.getName())) {
                return false;
            }
        }
        return true;
    }

    private void showTransientSnackbar(int resource) {
        Snackbar snackbar = Snackbar.make(parent, resource, Snackbar.LENGTH_SHORT);

        //View snackbarView = snackbar.getView();

        // change snackbar text color
        //int snackbarTextId = android.support.design.R.id.snackbar_text;
        //TextView textView = snackbarView.findViewById(snackbarTextId);
        //textView.setTextColor();
        //snackbarView.setBackgroundColor(Color.MAGENTA);
        snackbar.show();
    }

    private void setActivityCard(ViewHolder holder, final GBDevice device, long[] dailyTotals) {
        boolean showActivityCard = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()).getBoolean(DeviceSettingsPreferenceConst.PREFS_ACTIVITY_IN_DEVICE_CARD, true);
        holder.cardViewActivityCardLayout.setVisibility(showActivityCard ? View.VISIBLE : View.GONE);

        if (!showActivityCard) {
            return;
        }

        int steps = (int) dailyTotals[0];
        int sleep = (int) dailyTotals[1];
        ActivityUser activityUser = new ActivityUser();
        int stepGoal = activityUser.getStepsGoal();
        int sleepGoal = activityUser.getSleepDurationGoal();
        int sleepGoalMinutes = sleepGoal * 60;
        int distanceGoal = activityUser.getDistanceGoalMeters() * 100;
        int stepLength = activityUser.getStepLengthCm();
        double distanceMeters = dailyTotals[0] * stepLength * 0.01;
        String distanceFormatted = FormatUtils.getFormattedDistanceLabel(distanceMeters);

        setUpChart(holder.TotalStepsChart);
        setChartsData(holder.TotalStepsChart, steps, stepGoal, context.getString(R.string.steps), String.valueOf(steps), context);

        setUpChart(holder.TotalDistanceChart);
        setChartsData(holder.TotalDistanceChart, steps * stepLength, distanceGoal, context.getString(R.string.distance), distanceFormatted, context);

        setUpChart(holder.SleepTimeChart);
        setChartsData(holder.SleepTimeChart, sleep, sleepGoalMinutes, context.getString(R.string.prefs_activity_in_device_card_sleep_title), String.format("%1s", getHM(sleep)), context);


        boolean showActivitySteps = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()).getBoolean(DeviceSettingsPreferenceConst.PREFS_ACTIVITY_IN_DEVICE_CARD_STEPS, true);
        boolean showActivitySleep = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()).getBoolean(DeviceSettingsPreferenceConst.PREFS_ACTIVITY_IN_DEVICE_CARD_SLEEP, true);
        boolean showActivityDistance = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()).getBoolean(DeviceSettingsPreferenceConst.PREFS_ACTIVITY_IN_DEVICE_CARD_DISTANCE, true);

        //do the multiple mini-charts for activities in a loop
        Hashtable<PieChart, Pair<Boolean, Integer>> activitiesStatusMiniCharts = new Hashtable<>();
        activitiesStatusMiniCharts.put(holder.TotalStepsChart, new Pair<>(showActivitySteps && steps > 0, ActivityChartsActivity.getChartsTabIndex("stepsweek", device, context)));
        activitiesStatusMiniCharts.put(holder.SleepTimeChart, new Pair<>(showActivitySleep && sleep > 0, ActivityChartsActivity.getChartsTabIndex("sleep", device, context)));
        activitiesStatusMiniCharts.put(holder.TotalDistanceChart, new Pair<>(showActivityDistance && steps > 0, ActivityChartsActivity.getChartsTabIndex("activity", device, context)));

        for (Map.Entry<PieChart, Pair<Boolean, Integer>> miniCharts : activitiesStatusMiniCharts.entrySet()) {
            PieChart miniChart = miniCharts.getKey();
            final Pair<Boolean, Integer> parameters = miniCharts.getValue();
            miniChart.setVisibility(parameters.first ? View.VISIBLE : View.GONE);
            miniChart.setOnClickListener(new View.OnClickListener() {
                                             @Override
                                             public void onClick(View v) {
                                                 Intent startIntent;
                                                 startIntent = new Intent(context, ActivityChartsActivity.class);
                                                 startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
                                                 startIntent.putExtra(ActivityChartsActivity.EXTRA_FRAGMENT_ID, parameters.second);
                                                 context.startActivity(startIntent);
                                             }
                                         }
            );
        }
    }

    private String getHM(long value) {
        return DateTimeUtils.formatDurationHoursMinutes(value, TimeUnit.MINUTES);
    }
    private void setUpChart(PieChart DashboardChart) {
        DashboardChart.setTouchEnabled(false);
        DashboardChart.setNoDataText("");
        DashboardChart.getLegend().setEnabled(false);
        DashboardChart.setDrawHoleEnabled(true);
        DashboardChart.setHoleColor(Color.WHITE);
        DashboardChart.getDescription().setText("");
        DashboardChart.setTransparentCircleColor(Color.WHITE);
        DashboardChart.setTransparentCircleAlpha(110);
        DashboardChart.setHoleRadius(70f);
        DashboardChart.setTransparentCircleRadius(75f);
        DashboardChart.setDrawCenterText(true);
        DashboardChart.setRotationEnabled(true);
        DashboardChart.setHighlightPerTapEnabled(true);
        DashboardChart.setCenterTextOffset(0, 0);
    }
    private void setChartsData(PieChart pieChart, float value, float target, String label, String stringValue, Context context) {
        final String CHART_COLOR_START = "#e74c3c";
        final String CHART_COLOR_END = "#2ecc71";

        ArrayList<PieEntry> entries = new ArrayList<>();
        entries.add(new PieEntry((float) value, context.getResources().getDrawable(R.drawable.ic_star_gold)));

        if (value < target) {
            entries.add(new PieEntry((float) (target - value)));
        }

        pieChart.setCenterText(String.format("%s\n%s", stringValue, label));
        float colorValue = Math.max(0, Math.min(1, value / target));
        int chartColor = interpolateColor(Color.parseColor(CHART_COLOR_START), Color.parseColor(CHART_COLOR_END), colorValue);

        PieDataSet dataSet = new PieDataSet(entries, "");
        dataSet.setDrawIcons(false);
        dataSet.setIconsOffset(new MPPointF(0, -66));

        if (colorValue == 1) {
            dataSet.setDrawIcons(true);
        }
        dataSet.setSliceSpace(0f);
        dataSet.setSelectionShift(5f);
        dataSet.setColors(chartColor, Color.LTGRAY);

        PieData data = new PieData(dataSet);
        data.setValueTextSize(0f);
        data.setValueTextColor(Color.WHITE);

        pieChart.setData(data);
        pieChart.invalidate();
    }
    private float interpolate(float a, float b, float proportion) {
        return (a + ((b - a) * proportion));
    }

    private int interpolateColor(int a, int b, float proportion) {
        float[] hsva = new float[3];
        float[] hsvb = new float[3];
        Color.colorToHSV(a, hsva);
        Color.colorToHSV(b, hsvb);
        for (int i = 0; i < 3; i++) {
            hsvb[i] = interpolate(hsva[i], hsvb[i], proportion);
        }
        return Color.HSVToColor(hsvb);
    }

    @RequiresApi(api = Build.VERSION_CODES.R)
    void createDynamicShortcut(GBDevice device) {
        Intent intent = new Intent(context, ControlCenterv2.class)
                .setAction(ACTION_CONNECT)
                .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
                .putExtra("device", device.getAddress());

        ShortcutManager shortcutManager = (ShortcutManager) context.getApplicationContext().getSystemService(Context.SHORTCUT_SERVICE);

        DeviceCoordinator coordinator = device.getDeviceCoordinator();

        shortcutManager.pushDynamicShortcut(new ShortcutInfo.Builder(context, device.getAddress())
                .setLongLived(false)
                .setShortLabel(device.getAliasOrName())
                .setIntent(intent)
                .setIcon(Icon.createWithResource(context, coordinator.getDefaultIconResource()))
                .build()
        );
    }

    @RequiresApi(api = Build.VERSION_CODES.R)
    void removeDynamicShortcut(GBDevice device) {
        final ShortcutManager shortcutManager = (ShortcutManager) context.getApplicationContext().getSystemService(Context.SHORTCUT_SERVICE);

        shortcutManager.removeDynamicShortcuts(Collections.singletonList(device.getAddress()));
    }

    private static class GBDeviceDiffUtil extends DiffUtil.ItemCallback<GBDevice> {
        @Override
        public boolean areItemsTheSame(@NonNull GBDevice oldItem, @NonNull GBDevice newItem) {
            return new EqualsBuilder()
                    .append(oldItem.getAddress(), newItem.getAddress())
                    .append(oldItem.getName(), newItem.getName())
                    .isEquals();
        }

        @Override
        public boolean areContentsTheSame(@NonNull GBDevice oldItem, @NonNull GBDevice newItem) {
            return EqualsBuilder.reflectionEquals(oldItem, newItem);
        }
    }

    /**
     * A generator of stable IDs, given a string, since hashCode can easily have collisions.
     */
    private static class StableIdGenerator {
        private final Map<String, Long> idMapping = new HashMap<String, Long>();

        private long nextId = 0;

        public long getId(final GBDevice device) {
            final String str = String.format("%s_%s", device.getAddress(), device.getName());

            if (!idMapping.containsKey(str)) {
                idMapping.put(str, nextId++);
            }

            return idMapping.get(str);
        }
    }
}
