// Path: netty-http/src/main/java/http/exception/BadRequestException.java
// public class BadRequestException  extends Exception{
// 
//     public BadRequestException(Throwable throwable) {
//         super(throwable);
//     }
// }
// 
// Path: netty-http/src/main/java/http/message/DecodedSearchRequest.java
// public class DecodedSearchRequest {
//     private final HttpRequest httpRequest;
//     private final long orderNumber;
// 
//     private QueryMeta queryMeta;
// 
//     public DecodedSearchRequest(HttpRequest request, QueryMeta meta, long orderNumber) {
//         this.httpRequest = request;
//         this.queryMeta = meta;
//         this.orderNumber = orderNumber;
//     }
//     public QueryMeta getQueryMeta() {
//         return queryMeta;
//     }
// 
//     public void setQueryMeta(QueryMeta queryMeta) {
//         this.queryMeta = queryMeta;
//     }
// 
//     public HttpRequest getHttpRequest() {
//         return httpRequest;
//     }
// 
//     public long getOrderNumber() {
//         return orderNumber;
//     }
// 
//     @Override
//     public String toString() {
//         return "DecodedSearchRequest{" +
//                 "httpRequest=" + httpRequest +
//                 ", orderNumber=" + orderNumber +
//                 ", queryMeta=" + queryMeta +
//                 '}';
//     }
// }
// 
// Path: netty-http/src/main/java/http/message/QueryMeta.java
// public class QueryMeta {
// 
//     private String orderBy;
//     private HashMap<String, String> filters = new HashMap<String, String>();
// 
//     public HashMap addMeta(String type, String value) {
//         filters.put(type, value);
//         return filters;
//     }
// 
//     public String getOrderBy() {
//         return orderBy;
//     }
// 
//     public void setOrderBy(String orderBy) {
//         this.orderBy = orderBy;
//     }
// 
//     public HashMap<String, String> getFilters() {
//         return filters;
//     }
// 
//     public void setFilters(HashMap<String, String> filters) {
//         this.filters = filters;
//     }
// 
//     @Override
//     public String toString() {
//         return "QueryMeta{" +
//                 "orderBy='" + orderBy + '\'' +
//                 ", filters=" + filters +
//                 '}';
//     }
// }


import http.exception.BadRequestException;
import http.message.DecodedSearchRequest;
import http.message.QueryMeta;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.DecoderResult;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.QueryStringDecoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Map;

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest httpRequest) throws Exception {
        DecoderResult result = httpRequest.decoderResult();
        if (!result.isSuccess()) {
            throw new BadRequestException(result.cause());
        }
        logger.info("Get search request: " + httpRequest.uri());

        // only decode get path is enough
        Map<String, List<String>> requestParameters;
        QueryStringDecoder stringDecoder = new QueryStringDecoder(httpRequest.uri());
        requestParameters = stringDecoder.parameters();

        QueryMeta meta = new QueryMeta();

        for(Map.Entry<String, List<String>> entry : requestParameters.entrySet()) {
            if (entry.getKey().equals("options[]")) {
                // add filters
                List<String> filters = entry.getValue();
                filters.forEach(filter -> {
                    String[] typeVal = filter.split(":");
                    meta.addMeta(typeVal[0], typeVal[1]);
                });
            } else if (entry.getKey().equals("orderby")) {
                meta.setOrderBy(entry.getValue().get(0));
            } else {
                logger.warn("Unknown query parameter, ignore it:" + entry.toString());
            }
        }


// Path: netty-http/src/main/java/http/exception/BadRequestException.java
// public class BadRequestException  extends Exception{
// 
//     public BadRequestException(Throwable throwable) {
//         super(throwable);
//     }
// }
// 
// Path: netty-http/src/main/java/http/message/DecodedSearchRequest.java
// public class DecodedSearchRequest {
//     private final HttpRequest httpRequest;
//     private final long orderNumber;
// 
//     private QueryMeta queryMeta;
// 
//     public DecodedSearchRequest(HttpRequest request, QueryMeta meta, long orderNumber) {
//         this.httpRequest = request;
//         this.queryMeta = meta;
//         this.orderNumber = orderNumber;
//     }
//     public QueryMeta getQueryMeta() {
//         return queryMeta;
//     }
// 
//     public void setQueryMeta(QueryMeta queryMeta) {
//         this.queryMeta = queryMeta;
//     }
// 
//     public HttpRequest getHttpRequest() {
//         return httpRequest;
//     }
// 
//     public long getOrderNumber() {
//         return orderNumber;
//     }
// 
//     @Override
//     public String toString() {
//         return "DecodedSearchRequest{" +
//                 "httpRequest=" + httpRequest +
//                 ", orderNumber=" + orderNumber +
//                 ", queryMeta=" + queryMeta +
//                 '}';
//     }
// }
// 
// Path: netty-http/src/main/java/http/message/QueryMeta.java
// public class QueryMeta {
// 
//     private String orderBy;
//     private HashMap<String, String> filters = new HashMap<String, String>();
// 
//     public HashMap addMeta(String type, String value) {
//         filters.put(type, value);
//         return filters;
//     }
// 
//     public String getOrderBy() {
//         return orderBy;
//     }
// 
//     public void setOrderBy(String orderBy) {
//         this.orderBy = orderBy;
//     }
// 
//     public HashMap<String, String> getFilters() {
//         return filters;
//     }
// 
//     public void setFilters(HashMap<String, String> filters) {
//         this.filters = filters;
//     }
// 
//     @Override
//     public String toString() {
//         return "QueryMeta{" +
//                 "orderBy='" + orderBy + '\'' +
//                 ", filters=" + filters +
//                 '}';
//     }
// }


// Path: netty-http/src/main/java/http/SearchQueryDecoder.java
import http.exception.BadRequestException;
import http.message.DecodedSearchRequest;
import http.message.QueryMeta;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.DecoderResult;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.QueryStringDecoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Map;

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest httpRequest) throws Exception {
        DecoderResult result = httpRequest.decoderResult();
        if (!result.isSuccess()) {
            throw new BadRequestException(result.cause());
        }
        logger.info("Get search request: " + httpRequest.uri());

        // only decode get path is enough
        Map<String, List<String>> requestParameters;
        QueryStringDecoder stringDecoder = new QueryStringDecoder(httpRequest.uri());
        requestParameters = stringDecoder.parameters();

        QueryMeta meta = new QueryMeta();

        for(Map.Entry<String, List<String>> entry : requestParameters.entrySet()) {
            if (entry.getKey().equals("options[]")) {
                // add filters
                List<String> filters = entry.getValue();
                filters.forEach(filter -> {
                    String[] typeVal = filter.split(":");
                    meta.addMeta(typeVal[0], typeVal[1]);
                });
            } else if (entry.getKey().equals("orderby")) {
                meta.setOrderBy(entry.getValue().get(0));
            } else {
                logger.warn("Unknown query parameter, ignore it:" + entry.toString());
            }
        }


        DecodedSearchRequest searchRequest = new DecodedSearchRequest(httpRequest, meta, orderNumber++);
