/*
 * Copyright 2016-2018 The OpenTracing Authors
 *
 * Licensed 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 io.opentracing.contrib.web.servlet.filter;

import io.opentracing.propagation.TextMap;
import jakarta.servlet.http.HttpServletRequest;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Tracer extract adapter for {@link HttpServletRequest}.
 *
 * @author Pavol Loffay
 */
public class HttpServletRequestExtractAdapter implements TextMap {

  private Map<String, List<String>> headers;

  public HttpServletRequestExtractAdapter(HttpServletRequest httpServletRequest) {
    headers = servletHeadersToMultiMap(httpServletRequest);
  }

  @Override
  public Iterator<Map.Entry<String, String>> iterator() {
    return new MultivaluedMapFlatIterator<>(headers.entrySet());
  }

  @Override
  public void put(String key, String value) {
    throw new UnsupportedOperationException("This class should be used only with Tracer.inject()!");
  }

  protected Map<String, List<String>> servletHeadersToMultiMap(
      HttpServletRequest httpServletRequest) {
    Map<String, List<String>> headersResult = new HashMap<>();

    Enumeration<String> headerNamesIt = httpServletRequest.getHeaderNames();
    while (headerNamesIt.hasMoreElements()) {
      String headerName = headerNamesIt.nextElement();

      Enumeration<String> valuesIt = httpServletRequest.getHeaders(headerName);
      List<String> valuesList = new ArrayList<>(1);
      while (valuesIt.hasMoreElements()) {
        valuesList.add(valuesIt.nextElement());
      }

      headersResult.put(headerName, valuesList);
    }

    return headersResult;
  }

  public static final class MultivaluedMapFlatIterator<K, V> implements Iterator<Map.Entry<K, V>> {

    private final Iterator<Map.Entry<K, List<V>>> mapIterator;
    private Map.Entry<K, List<V>> mapEntry;
    private Iterator<V> listIterator;

    public MultivaluedMapFlatIterator(Set<Map.Entry<K, List<V>>> multiValuesEntrySet) {
      this.mapIterator = multiValuesEntrySet.iterator();
    }

    @Override
    public boolean hasNext() {
      if (listIterator != null && listIterator.hasNext()) {
        return true;
      }

      return mapIterator.hasNext();
    }

    @Override
    public Map.Entry<K, V> next() {
      if (mapEntry == null || (!listIterator.hasNext() && mapIterator.hasNext())) {
        mapEntry = mapIterator.next();
        listIterator = mapEntry.getValue().iterator();
      }

      if (listIterator.hasNext()) {
        return new AbstractMap.SimpleImmutableEntry<>(mapEntry.getKey(), listIterator.next());
      } else {
        return new AbstractMap.SimpleImmutableEntry<>(mapEntry.getKey(), null);
      }
    }

    @Override
    public void remove() {
      throw new UnsupportedOperationException();
    }
  }
}
