class DefaultTimepointLimiter implements TimepointLimiter {

    @Override
    public @NonNull Timepoint roundToNearest(@NonNull Timepoint time,@Nullable Timepoint.TYPE type, @NonNull Timepoint.TYPE resolution) {
        if (mMinTime != null && mMinTime.compareTo(time) > 0) return mMinTime;

        if (mMaxTime != null && mMaxTime.compareTo(time) < 0) return mMaxTime;

        // type == SECOND: cannot change anything, return input
        if (type == Timepoint.TYPE.SECOND) return time;

        if (!exclusiveSelectableTimes.isEmpty()) {
            Timepoint floor = exclusiveSelectableTimes.floor(time);
            Timepoint ceil = exclusiveSelectableTimes.ceiling(time);

            if (floor == null || ceil == null) {
                Timepoint t = floor == null ? ceil : floor;
                if (type == null) return t;
                if (t.getHour() != time.getHour()) return time;
                if (type == Timepoint.TYPE.MINUTE && t.getMinute() != time.getMinute()) return time;
                return t;
            }

            if (type == Timepoint.TYPE.HOUR) {
                if (floor.getHour() != time.getHour() && ceil.getHour() == time.getHour()) return ceil;
                if (floor.getHour() == time.getHour() && ceil.getHour() != time.getHour()) return floor;
                if (floor.getHour() != time.getHour() && ceil.getHour() != time.getHour()) return time;
            }

            if (type == Timepoint.TYPE.MINUTE) {
                if (floor.getHour() != time.getHour() && ceil.getHour() != time.getHour()) return time;
                if (floor.getHour() != time.getHour() && ceil.getHour() == time.getHour()) {
                    return ceil.getMinute() == time.getMinute() ? ceil : time;
                }
                if (floor.getHour() == time.getHour() && ceil.getHour() != time.getHour()) {
                    return floor.getMinute() == time.getMinute() ? floor : time;
                }
                if (floor.getMinute() != time.getMinute() && ceil.getMinute() == time.getMinute()) return ceil;
                if (floor.getMinute() == time.getMinute() && ceil.getMinute() != time.getMinute()) return floor;
                if (floor.getMinute() != time.getMinute() && ceil.getMinute() != time.getMinute()) return time;
            }

            int floorDist = Math.abs(time.compareTo(floor));
            int ceilDist = Math.abs(time.compareTo(ceil));

            return floorDist < ceilDist ? floor : ceil;
        }

        if (!mDisabledTimes.isEmpty()) {
            // if type matches resolution: cannot change anything, return input
            if (type != null && type == resolution) return time;

            if (resolution == Timepoint.TYPE.SECOND) {
                if (!mDisabledTimes.contains(time)) return time;
                return searchValidTimePoint(time, type, resolution);
            }

            if (resolution == Timepoint.TYPE.MINUTE) {
                Timepoint ceil = mDisabledTimes.ceiling(time);
                Timepoint floor = mDisabledTimes.floor(time);
                boolean ceilDisabled = time.equals(ceil, Timepoint.TYPE.MINUTE);
                boolean floorDisabled = time.equals(floor, Timepoint.TYPE.MINUTE);

                if (ceilDisabled || floorDisabled) return searchValidTimePoint(time, type, resolution);
                return time;
            }

            if (resolution == Timepoint.TYPE.HOUR) {
                Timepoint ceil = mDisabledTimes.ceiling(time);
                Timepoint floor = mDisabledTimes.floor(time);
                boolean ceilDisabled = time.equals(ceil, Timepoint.TYPE.HOUR);
                boolean floorDisabled = time.equals(floor, Timepoint.TYPE.HOUR);

                if (ceilDisabled || floorDisabled) return searchValidTimePoint(time, type, resolution);
                return time;
            }
        }

        return time;
    }

      DefaultTimepointLimiter();
    @SuppressWarnings("WeakerAccess") public  DefaultTimepointLimiter(Parcel in);

}