// Path: marketdata-common/src/main/java/com/handson/dto/Quote.java
// public class Quote {
// 
//     public final String code;
//     public final double quote;
// 
//     public Quote(String code, double quote) {
//         this.code = code;
//         this.quote = quote;
//     }
// 
//     public static Quote fromJson(String input) {
//         return new Gson().fromJson(input, Quote.class);
//     }
// 
//     public String toJson() {
//         return new Gson().toJson(this);
//     }
// 
//     @Override
//     public String toString() {
//         return toJson();
//     }
// }
// 
// Path: marketdata-common/src/main/java/com/handson/infra/RandomSequenceGenerator.java
// public class RandomSequenceGenerator {
// 
//     private final double min;
//     private final double max;
//     private final Random random;
//     private double bias = 0.3;
// 
//     public RandomSequenceGenerator(double min, double max) {
//         this.min = min;
//         this.max = max;
//         this.random = new Random();
//     }
// 
//     public Observable<Double> create(long interval, TimeUnit timeUnit) {
//         double init = (min + max) /2;
//         return Observable
//                 .interval(interval, timeUnit)
//                 .scan(init, (previous, i) -> computeNextNumber(previous));
//     }
// 
//     public Observable<Integer> createIntegerSequence(long interval, TimeUnit timeUnit) {
//         double range = max - min;
//         return Observable
//                 .interval(interval, timeUnit)
//                 .map(i ->  ((Double)(random.nextDouble() * range  + min)).intValue());
//     }
// 
//     public double computeNextNumber(double previous) {
//         double range = (max - min) / 20;
//         double scaled = (random.nextDouble() - 0.5 + bias) * range;
//         double shifted = previous + scaled;
//         if (shifted < min || shifted > max) {
//             shifted = previous - scaled;
//             bias = -bias;
//         }
// 
//         shifted = ((Long)Math.round(shifted * 10000)).doubleValue() /10000;
// 
//         return shifted;
//     }
// 
// }
// 
// Path: marketdata-common/src/main/java/com/handson/infra/RxNettyEventBroadcaster.java
// public abstract class RxNettyEventBroadcaster<T> extends RxNettyEventServer {
// 
// 
//     private final boolean flaky;
//     private Observable<T> events;
// 
//     public RxNettyEventBroadcaster(int port, boolean flaky) {
//         super(port);
//         this.flaky = flaky;
//     }
// 
//     public HttpServer<ByteBuf, ServerSentEvent> createServer() {
//         if (flaky) {
//             events = SubscriptionLimiter
//                         .limitSubscriptions(1,initializeEventStream());
//         } else {
//             events  = initializeEventStream();
//         }
//         return super.createServer();
//     }
// 
//     protected abstract Observable<T> initializeEventStream();
// 
//     public Observable<T> getEvents(HttpRequest request) {
//         return events;
//     }
// 
// }


import com.handson.dto.Quote;
import com.handson.infra.RandomSequenceGenerator;
import com.handson.infra.RxNettyEventBroadcaster;
import rx.Observable;
import java.util.concurrent.TimeUnit;
package com.handson.market;




public class StockQuoteProvider extends RxNettyEventBroadcaster<Quote> {

    public static final int PORT = 8098;

    private static final double GOOGLE_MIN = 116;
    private static final double GOOGLE_MAX = 136;
    private static final double IBM_MIN = 120;
    private static final double IBM_MAX = 135;
    private static final double HP_MIN = 12.5;
    private static final double HP_MAX = 14;
    private static final double APPLE_MIN = 94;
    private static final double APPLE_MAX = 99;
    private static final double MICROSOFT_MIN = 51;
    private static final double MICROSOFT_MAX = 53;


    public StockQuoteProvider(int port, boolean flaky) {
        super(port, flaky);
    }

    @Override
    protected Observable<Quote> initializeEventStream() {
        Observable<Quote> googleStock
                = new RandomSequenceGenerator(GOOGLE_MIN, GOOGLE_MAX)
                    .create(200, TimeUnit.MILLISECONDS)
                    .map(s -> new Quote("GOOGL", s));
        Observable<Quote> ibmStock
// Path: marketdata-common/src/main/java/com/handson/dto/Quote.java
// public class Quote {
// 
//     public final String code;
//     public final double quote;
// 
//     public Quote(String code, double quote) {
//         this.code = code;
//         this.quote = quote;
//     }
// 
//     public static Quote fromJson(String input) {
//         return new Gson().fromJson(input, Quote.class);
//     }
// 
//     public String toJson() {
//         return new Gson().toJson(this);
//     }
// 
//     @Override
//     public String toString() {
//         return toJson();
//     }
// }
// 
// Path: marketdata-common/src/main/java/com/handson/infra/RandomSequenceGenerator.java
// public class RandomSequenceGenerator {
// 
//     private final double min;
//     private final double max;
//     private final Random random;
//     private double bias = 0.3;
// 
//     public RandomSequenceGenerator(double min, double max) {
//         this.min = min;
//         this.max = max;
//         this.random = new Random();
//     }
// 
//     public Observable<Double> create(long interval, TimeUnit timeUnit) {
//         double init = (min + max) /2;
//         return Observable
//                 .interval(interval, timeUnit)
//                 .scan(init, (previous, i) -> computeNextNumber(previous));
//     }
// 
//     public Observable<Integer> createIntegerSequence(long interval, TimeUnit timeUnit) {
//         double range = max - min;
//         return Observable
//                 .interval(interval, timeUnit)
//                 .map(i ->  ((Double)(random.nextDouble() * range  + min)).intValue());
//     }
// 
//     public double computeNextNumber(double previous) {
//         double range = (max - min) / 20;
//         double scaled = (random.nextDouble() - 0.5 + bias) * range;
//         double shifted = previous + scaled;
//         if (shifted < min || shifted > max) {
//             shifted = previous - scaled;
//             bias = -bias;
//         }
// 
//         shifted = ((Long)Math.round(shifted * 10000)).doubleValue() /10000;
// 
//         return shifted;
//     }
// 
// }
// 
// Path: marketdata-common/src/main/java/com/handson/infra/RxNettyEventBroadcaster.java
// public abstract class RxNettyEventBroadcaster<T> extends RxNettyEventServer {
// 
// 
//     private final boolean flaky;
//     private Observable<T> events;
// 
//     public RxNettyEventBroadcaster(int port, boolean flaky) {
//         super(port);
//         this.flaky = flaky;
//     }
// 
//     public HttpServer<ByteBuf, ServerSentEvent> createServer() {
//         if (flaky) {
//             events = SubscriptionLimiter
//                         .limitSubscriptions(1,initializeEventStream());
//         } else {
//             events  = initializeEventStream();
//         }
//         return super.createServer();
//     }
// 
//     protected abstract Observable<T> initializeEventStream();
// 
//     public Observable<T> getEvents(HttpRequest request) {
//         return events;
//     }
// 
// }


// Path: marketdata-external/src/main/java/com/handson/market/StockQuoteProvider.java
import com.handson.dto.Quote;
import com.handson.infra.RandomSequenceGenerator;
import com.handson.infra.RxNettyEventBroadcaster;
import rx.Observable;
import java.util.concurrent.TimeUnit;

package com.handson.market;




public class StockQuoteProvider extends RxNettyEventBroadcaster<Quote> {

    public static final int PORT = 8098;

    private static final double GOOGLE_MIN = 116;
    private static final double GOOGLE_MAX = 136;
    private static final double IBM_MIN = 120;
    private static final double IBM_MAX = 135;
    private static final double HP_MIN = 12.5;
    private static final double HP_MAX = 14;
    private static final double APPLE_MIN = 94;
    private static final double APPLE_MAX = 99;
    private static final double MICROSOFT_MIN = 51;
    private static final double MICROSOFT_MAX = 53;


    public StockQuoteProvider(int port, boolean flaky) {
        super(port, flaky);
    }

    @Override
    protected Observable<Quote> initializeEventStream() {
        Observable<Quote> googleStock
                = new RandomSequenceGenerator(GOOGLE_MIN, GOOGLE_MAX)
                    .create(200, TimeUnit.MILLISECONDS)
                    .map(s -> new Quote("GOOGL", s));
        Observable<Quote> ibmStock
                = new RandomSequenceGenerator(IBM_MIN, IBM_MAX)