// Path: pushbullet/src/main/java/plugins/pushbullet/api/PushbulletService.java
// public interface PushbulletService {
//     @GET("users/me")
//     Single<User> getUser();
// 
//     @GET("channels")
//     Single<Channels> getChannels();
// 
//     @GET("devices")
//     Single<Devices> getDevices();
// 
//     @POST("pushes")
//     Single<Pushes> push(@Body PushParameter param);
// }
// 
// Path: pushbullet/src/main/java/plugins/pushbullet/api/Pusher.java
// public class Pusher {
//     private String accessToken;
// 
//     public Pusher(String accessToken) {
//         this.accessToken = accessToken;
//     }
// 
//     /**
//      * 設定で選択した端末およびチャンネルにプッシュする
//      *
//      * @param title   String
//      * @param message String
//      */
//     public void pushToSelectedTargets(String title, String message) {
//         pushToSelectedTargets(title, message, null, null);
//     }
// 
//     /**
//      * 設定で選択した端末およびチャンネルにプッシュする
//      *
//      * @param title   String
//      * @param message String
//      * @param onSuccess Consumer&lt;Pushes&gt;
//      * @param onError Consumer&lt;Throwable&gt;
//      */
//     public void pushToSelectedTargets(String title,
//                                       String message,
//                                       Consumer<Pushes> onSuccess,
//                                       Consumer<Throwable> onError) {
//         PushbulletService service = ServiceFactory.create(accessToken);
//         Consumer<Pushes> _onSuccess = (onSuccess != null) ? onSuccess : pushes -> {};
//         Consumer<Throwable> _onError = (onError != null) ? onError : LoggerHolder::logError;
// 
//         DeviceCollection.get()
//                 .stream()
//                 .filter(Device::isActive)
//                 .filter(Device::isSelected)
//                 .forEach(device -> service.push(PushParameter.noteToDevice(device, title, message))
//                         .subscribeOn(Schedulers.io())
//                         .observeOn(Schedulers.computation())
//                         .subscribe(_onSuccess, _onError));
// 
//         ChannelCollection.get()
//                 .stream()
//                 .filter(Channel::isActive)
//                 .filter(Channel::isSelected)
//                 .forEach(channel -> service.push(PushParameter.noteToChannel(channel, title, message))
//                         .subscribeOn(Schedulers.io())
//                         .observeOn(Schedulers.computation())
//                         .subscribe(_onSuccess, _onError));
//     }
// 
//     private static class LoggerHolder {
//         /**
//          * ロガー
//          */
//         private static final Logger LOG = LogManager.getLogger(Pusher.class);
// 
//         private static void logError(Throwable e) {
//             LOG.error(e.getMessage(), e);
//         }
//     }
// }
// 
// Path: pushbullet/src/main/java/plugins/pushbullet/api/ServiceFactory.java
// public class ServiceFactory {
//     private static final String BASE_URL = "https://api.pushbullet.com/v2/";
// 
//     public static PushbulletService create(String accessToken) {
//         return createRetrofit(accessToken).create(PushbulletService.class);
//     }
// 
//     private static Retrofit createRetrofit(String accessToken) {
//         return new Retrofit.Builder()
//                 .baseUrl(BASE_URL)
//                 .client(createClient(accessToken))
//                 .addConverterFactory(GsonConverterFactory.create())
//                 .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
//                 .build();
//     }
// 
//     private static OkHttpClient createClient(String accessToken) {
//         return new OkHttpClient.Builder()
//                 .addInterceptor(chain -> chain.proceed(chain.request()
//                         .newBuilder()
//                         .addHeader("Access-Token", accessToken)
//                         .build()))
//                 .build();
//     }
// }


import io.reactivex.Observable;
import io.reactivex.rxjavafx.schedulers.JavaFxScheduler;
import io.reactivex.schedulers.Schedulers;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.control.cell.CheckBoxListCell;
import logbook.internal.Config;
import logbook.internal.ThreadManager;
import logbook.internal.gui.WindowController;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import plugins.pushbullet.api.PushbulletService;
import plugins.pushbullet.api.Pusher;
import plugins.pushbullet.api.ServiceFactory;
import plugins.pushbullet.bean.*;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
    private ListView<Channel> channelListView;
    private ObservableList<Channel> channels = FXCollections.observableArrayList();
    private Map<String, Boolean> channelSelectedMap = new ConcurrentHashMap<>();

    private void addDevice(Device device) {
        devices.add(device);
        deviceSelectedMap.put(device.getIdentity(), device.isSelected());
    }

    private Device markDeviceSelected(Device device) {
        device.setSelected(deviceSelectedMap.getOrDefault(device.getIdentity(), false));
        return device;
    }

    private void addChannel(Channel channel) {
        channels.add(channel);
        channelSelectedMap.put(channel.getTag(), channel.isSelected());
    }

    private Channel markChannelSelected(Channel channel) {
        channel.setSelected(channelSelectedMap.getOrDefault(channel.getTag(), false));
        return channel;
    }

    private <E> void setUpListChangeListener(ObservableList<E> list, ListView<E> listView) {
        final double CELL_HEIGHT = 25.0;
        list.addListener((ListChangeListener<E>) c -> {
            final int size = list.size();
            switch (size) {
                case 0:
// Path: pushbullet/src/main/java/plugins/pushbullet/api/PushbulletService.java
// public interface PushbulletService {
//     @GET("users/me")
//     Single<User> getUser();
// 
//     @GET("channels")
//     Single<Channels> getChannels();
// 
//     @GET("devices")
//     Single<Devices> getDevices();
// 
//     @POST("pushes")
//     Single<Pushes> push(@Body PushParameter param);
// }
// 
// Path: pushbullet/src/main/java/plugins/pushbullet/api/Pusher.java
// public class Pusher {
//     private String accessToken;
// 
//     public Pusher(String accessToken) {
//         this.accessToken = accessToken;
//     }
// 
//     /**
//      * 設定で選択した端末およびチャンネルにプッシュする
//      *
//      * @param title   String
//      * @param message String
//      */
//     public void pushToSelectedTargets(String title, String message) {
//         pushToSelectedTargets(title, message, null, null);
//     }
// 
//     /**
//      * 設定で選択した端末およびチャンネルにプッシュする
//      *
//      * @param title   String
//      * @param message String
//      * @param onSuccess Consumer&lt;Pushes&gt;
//      * @param onError Consumer&lt;Throwable&gt;
//      */
//     public void pushToSelectedTargets(String title,
//                                       String message,
//                                       Consumer<Pushes> onSuccess,
//                                       Consumer<Throwable> onError) {
//         PushbulletService service = ServiceFactory.create(accessToken);
//         Consumer<Pushes> _onSuccess = (onSuccess != null) ? onSuccess : pushes -> {};
//         Consumer<Throwable> _onError = (onError != null) ? onError : LoggerHolder::logError;
// 
//         DeviceCollection.get()
//                 .stream()
//                 .filter(Device::isActive)
//                 .filter(Device::isSelected)
//                 .forEach(device -> service.push(PushParameter.noteToDevice(device, title, message))
//                         .subscribeOn(Schedulers.io())
//                         .observeOn(Schedulers.computation())
//                         .subscribe(_onSuccess, _onError));
// 
//         ChannelCollection.get()
//                 .stream()
//                 .filter(Channel::isActive)
//                 .filter(Channel::isSelected)
//                 .forEach(channel -> service.push(PushParameter.noteToChannel(channel, title, message))
//                         .subscribeOn(Schedulers.io())
//                         .observeOn(Schedulers.computation())
//                         .subscribe(_onSuccess, _onError));
//     }
// 
//     private static class LoggerHolder {
//         /**
//          * ロガー
//          */
//         private static final Logger LOG = LogManager.getLogger(Pusher.class);
// 
//         private static void logError(Throwable e) {
//             LOG.error(e.getMessage(), e);
//         }
//     }
// }
// 
// Path: pushbullet/src/main/java/plugins/pushbullet/api/ServiceFactory.java
// public class ServiceFactory {
//     private static final String BASE_URL = "https://api.pushbullet.com/v2/";
// 
//     public static PushbulletService create(String accessToken) {
//         return createRetrofit(accessToken).create(PushbulletService.class);
//     }
// 
//     private static Retrofit createRetrofit(String accessToken) {
//         return new Retrofit.Builder()
//                 .baseUrl(BASE_URL)
//                 .client(createClient(accessToken))
//                 .addConverterFactory(GsonConverterFactory.create())
//                 .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
//                 .build();
//     }
// 
//     private static OkHttpClient createClient(String accessToken) {
//         return new OkHttpClient.Builder()
//                 .addInterceptor(chain -> chain.proceed(chain.request()
//                         .newBuilder()
//                         .addHeader("Access-Token", accessToken)
//                         .build()))
//                 .build();
//     }
// }


// Path: pushbullet/src/main/java/plugins/pushbullet/gui/PushbulletConfigController.java
import io.reactivex.Observable;
import io.reactivex.rxjavafx.schedulers.JavaFxScheduler;
import io.reactivex.schedulers.Schedulers;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.control.cell.CheckBoxListCell;
import logbook.internal.Config;
import logbook.internal.ThreadManager;
import logbook.internal.gui.WindowController;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import plugins.pushbullet.api.PushbulletService;
import plugins.pushbullet.api.Pusher;
import plugins.pushbullet.api.ServiceFactory;
import plugins.pushbullet.bean.*;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

    private ListView<Channel> channelListView;
    private ObservableList<Channel> channels = FXCollections.observableArrayList();
    private Map<String, Boolean> channelSelectedMap = new ConcurrentHashMap<>();

    private void addDevice(Device device) {
        devices.add(device);
        deviceSelectedMap.put(device.getIdentity(), device.isSelected());
    }

    private Device markDeviceSelected(Device device) {
        device.setSelected(deviceSelectedMap.getOrDefault(device.getIdentity(), false));
        return device;
    }

    private void addChannel(Channel channel) {
        channels.add(channel);
        channelSelectedMap.put(channel.getTag(), channel.isSelected());
    }

    private Channel markChannelSelected(Channel channel) {
        channel.setSelected(channelSelectedMap.getOrDefault(channel.getTag(), false));
        return channel;
    }

    private <E> void setUpListChangeListener(ObservableList<E> list, ListView<E> listView) {
        final double CELL_HEIGHT = 25.0;
        list.addListener((ListChangeListener<E>) c -> {
            final int size = list.size();
            switch (size) {
                case 0:
                    listView.setMinHeight(0.0);