// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

package org.openqa.selenium.environment.webserver;

import com.google.common.base.Splitter;
import org.openqa.selenium.Cookie;
import org.openqa.selenium.remote.http.Contents;
import org.openqa.selenium.remote.http.HttpHandler;
import org.openqa.selenium.remote.http.HttpRequest;
import org.openqa.selenium.remote.http.HttpResponse;

import java.io.UncheckedIOException;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.temporal.TemporalAccessor;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import static java.nio.charset.StandardCharsets.UTF_8;
import static java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME;

public class CookieHandler implements HttpHandler {

  private static final String EPOCH_START = RFC_1123_DATE_TIME.format(
    ZonedDateTime.ofInstant(Instant.ofEpochMilli(0), ZoneId.of("UTC")));
  private static final String RESPONSE_STRING =
      "<html><head><title>Done</title></head><body>%s : %s</body></html>";

  @Override
  public HttpResponse execute(HttpRequest request) throws UncheckedIOException {
    HttpResponse response = new HttpResponse();
    response.setHeader("Content-Type", "text/html");
    //Dont Cache Anything  at the browser
    response.setHeader("Cache-Control","no-cache");
    response.setHeader("Pragma","no-cache");
    response.setHeader ("Expires", EPOCH_START);


    String action = request.getQueryParameter("action");

    if ("add".equals(action)) {
      String name = request.getQueryParameter("name");

      StringBuilder cookie = new StringBuilder();
      cookie.append(name)
        .append("=")
        .append(request.getQueryParameter("value"))
        .append("; ");

      append(cookie, request.getQueryParameter("domain"), str -> "Domain=" + str);
      append(cookie, request.getQueryParameter("path"), str -> "Path=" + str);
      append(cookie, request.getQueryParameter("expiry"), str -> "Max-Age=" + Integer.parseInt(str));
      append(cookie, request.getQueryParameter( "secure"), str -> "Secure");
      append(cookie, request.getQueryParameter( "httpOnly"), str -> "HttpOnly");

      response.addHeader("Set-Cookie", cookie.toString());

      response.setContent(Contents.string(String.format(RESPONSE_STRING, "Cookie added", name), UTF_8));
    } else if ("delete".equals(action)) {
      String name = request.getQueryParameter("name");
      for (Cookie cookie : getCookies(request)) {
        if (!cookie.getName().equals(name)) {
          addCookie(response, new Cookie.Builder(name, "").path("/").expiresOn(new Date(0)).build());
        }
      }
      response.setContent(Contents.string(String.format(RESPONSE_STRING, "Cookie deleted", name), UTF_8));
    } else if ("deleteAll".equals(action)) {
      for (Cookie cookie : getCookies(request)) {
        addCookie(response, new Cookie.Builder(cookie.getName(), "").path("/").expiresOn(new Date(0)).build());
      }
      response.setContent(Contents.string(String.format(RESPONSE_STRING, "All cookies deleted", ""), UTF_8));
    } else {
      response.setContent(Contents.string(String.format(RESPONSE_STRING, "Unrecognized action", action), UTF_8));
    }

    return response;
  }

  private <X> void append(StringBuilder builder, X fromCookie, Function<X, String> value) {
    if (fromCookie == null) {
      return;
    }

    builder.append(value.apply(fromCookie)).append("; ");
  }

  private Collection<Cookie> getCookies(HttpRequest request) {
    return StreamSupport.stream(request.getHeaders("Cookie").spliterator(), false)
      .map(this::parse)
      .collect(Collectors.toList());
  }

  private void addCookie(HttpResponse response, Cookie cook) {
    StringBuilder cookie = new StringBuilder();

    // TODO: escape string as necessary
    String name = cook.getName();
    cookie.append(name)
      .append("=")
      .append(cook.getValue())
      .append("; ");

    append(cookie, cook.getDomain(), str -> "Domain=" + str);
    append(cookie, cook.getPath(), str -> "Path=" + str);
    append(cookie, cook.getExpiry(), date -> {
      ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(date.toInstant(), ZoneId.of("UTC"));
      return "Expiry=" + RFC_1123_DATE_TIME.format(zonedDateTime);
    });
    append(cookie, cook.isSecure(), val -> "Secure");
    append(cookie, cook.isHttpOnly(), val -> "HttpOnly");

    response.addHeader("Set-Cookie", cookie.toString());
  }

  private Cookie parse(String cookieString) {
    String[] split = cookieString.split("=", 2);

    if (split.length < 2) {
      throw new IllegalStateException("Illegal cookie: " + cookieString);
    }

    int index = split[1].indexOf(";");
    if (index == -1) {
      return new Cookie(split[0], split[1]);
    }

    List<String> keysAndValues = Splitter.on(";").trimResults().omitEmptyStrings().splitToList(split[1]);
    Cookie.Builder builder = new Cookie.Builder(split[0], keysAndValues.get(0));

    keysAndValues.stream()
      .skip(1)
      .forEach(keyAndValue -> {
        List<String> parts = Splitter.on("=").limit(2).trimResults().omitEmptyStrings().splitToList(keyAndValue);
        String key = parts.get(0).toLowerCase();
        switch (key) {
          case "domain":
            builder.domain(parts.get(1));
            break;

          case "expires":
            TemporalAccessor temporalAccessor = RFC_1123_DATE_TIME.parse(parts.get(1));
            builder.expiresOn(Date.from(Instant.from(temporalAccessor)));
            break;

          case "httponly":
            builder.isHttpOnly(true);
            break;

          case "path":
            builder.path(parts.get(1));
            break;

          case "secure":
            builder.isSecure(true);
            break;
        }
      });

    return builder.build();
  }
}

@@ -6708,16 +6708,102 @@
   break;
+%0A%0A          default:%0A            throw new RuntimeException(%22Unknown option: %22 + key);
 %0A       

Fix failing spotbugs test