package org.ambraproject.wombat.controller;

import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Strings;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Ordering;
import com.google.common.collect.Table;
import org.ambraproject.wombat.config.site.RequestHandlerPatternDictionary;
import org.ambraproject.wombat.config.site.RequestMappingValue;
import org.ambraproject.wombat.config.site.Site;
import org.ambraproject.wombat.config.site.SiteSet;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.ServletContext;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * A pseudo-controller that is dispatched to by {@link org.ambraproject.wombat.controller.ExceptionHandlerAdvisor}.
 * Autowired as a normal Spring bean.
 */
public class AppRootPage {

  private static final Logger log = LoggerFactory.getLogger(AppRootPage.class);

  @Autowired
  private SiteSet siteSet;
  @Autowired
  private ServletContext servletContext;
  @Autowired
  private RequestHandlerPatternDictionary requestHandlerPatternDictionary;

  /**
   * Show a page in response to the application root.
   * <p/>
   * This is here only for development/debugging: if you browse to the application root while you're setting up, this
   * page is more useful than an error message. But all end-user-facing pages should belong to one of the sites in
   * {@code siteSet}.
   */
  ModelAndView serveAppRoot() {
    ModelAndView mav = new ModelAndView("//approot");
    mav.addObject("siteKeys", siteSet.getSiteKeys());
    mav.addObject("mappingTable", buildMappingTable());
    try {
      mav.addObject("imageCode", getResourceAsBase64("/WEB-INF/themes/root/app/wombat.jpg"));
    } catch (IOException e) {
      log.error("Error displaying root page image", e);
    }
    return mav;
  }

  private String getResourceAsBase64(String path) throws IOException {
    byte[] bytes;
    try (InputStream stream = servletContext.getResourceAsStream(path)) {
      bytes = IOUtils.toByteArray(stream);
    }
    return Base64.encodeBase64String(bytes);
  }

  private ImmutableList<MappingTableRow> buildMappingTable() {
    Table<RequestMappingValue, Site, String> table = HashBasedTable.create();
    Set<RequestMappingValue> sitelessMappings = new HashSet<>();
    ImmutableList<Site> allSites = siteSet.getSites().asList();
    for (RequestHandlerPatternDictionary.MappingEntry entry : requestHandlerPatternDictionary.getAll()) {
      RequestMappingValue mapping = entry.getMapping();
      Optional<Site> site = entry.getSite();
      String handlerName = entry.getHandlerName();

      if (site.isPresent()) {
        table.put(mapping, site.get(), handlerName);
      } else {
        sitelessMappings.add(mapping);
        for (Site s : allSites) {
          table.put(mapping, s, handlerName);
        }
      }
    }

    Set<RequestMappingValue> mappings = table.rowKeySet();
    List<MappingTableRow> rows = new ArrayList<>(mappings.size());
    for (final RequestMappingValue mapping : mappings) {
      final List<String> row = new ArrayList<>(allSites.size());
      for (Site site : allSites) {
        String cell = table.get(mapping, site);
        row.add(Strings.nullToEmpty(cell));
      }
      final boolean isGlobal = sitelessMappings.contains(mapping);

      rows.add(new MappingTableRow() {
        @Override
        public String getPattern() {
          return mapping.getPattern();
        }

        @Override
        public List<String> getRow() {
          return row;
        }

        @Override
        public boolean isGlobal() {
          return isGlobal;
        }
      });
    }

    return ROW_ORDERING.immutableSortedCopy(rows);
  }

  public static interface MappingTableRow {
    String getPattern();

    List<String> getRow();

    boolean isGlobal();
  }

  private static final Ordering<MappingTableRow> ROW_ORDERING = Ordering.natural().onResultOf(
      new Function<MappingTableRow, String>() {
        @Override
        public String apply(MappingTableRow row) {
          return row.getPattern();
        }
      });

}

@@ -3640,24 +3640,87 @@
 ));%0A      %7D%0A
+      final String mappingRepresentation = represent(mapping);%0A
       final 
@@ -3774,16 +3774,16 @@
 pping);%0A
-
 %0A      r
@@ -3893,29 +3893,30 @@
  mapping
-.getPattern()
+Representation
 ;%0A      
@@ -4114,24 +4114,24 @@
  %7D);%0A    %7D%0A%0A
-
     return R
@@ -4174,16 +4174,365 @@
 );%0A  %7D%0A%0A
+  private static String represent(RequestMappingValue mapping) %7B%0A    StringBuilder sb = new StringBuilder().append(mapping.getPattern());%0A    boolean atFirst = true;%0A    for (String requiredParam : mapping.getRequiredParams()) %7B%0A      sb.append(atFirst ? '?' : '&').append(requiredParam);%0A      atFirst = false;%0A    %7D%0A    return sb.toString();%0A  %7D%0A%0A
   public

Include query parameters when listing patterns