/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * 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 com.android.vts.servlet;

import com.android.vts.entity.ProfilingPointEntity;
import com.android.vts.entity.ProfilingPointSummaryEntity;
import com.android.vts.util.BoxPlot;
import com.android.vts.util.DatastoreHelper;
import com.android.vts.util.FilterUtil;
import com.android.vts.util.GraphSerializer;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.Filter;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.stream.Collectors;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/** Servlet for handling requests to load graphs. */
public class ShowProfilingOverviewServlet extends BaseServlet {
    private static final String PROFILING_OVERVIEW_JSP = "WEB-INF/jsp/show_profiling_overview.jsp";

    @Override
    public PageType getNavParentType() {
        return PageType.PROFILING_LIST;
    }

    @Override
    public List<Page> getBreadcrumbLinks(HttpServletRequest request) {
        List<Page> links = new ArrayList<>();
        String testName = request.getParameter("testName");
        links.add(new Page(PageType.PROFILING_OVERVIEW, testName, "?testName=" + testName));
        return links;
    }

    @Override
    public void doGetHandler(HttpServletRequest request, HttpServletResponse response)
            throws IOException {
        RequestDispatcher dispatcher = null;
        DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

        String testName = request.getParameter("testName");
        long endTime = TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis());
        long startTime = endTime - TimeUnit.DAYS.toMicros(30);

        // Create a query for test runs matching the time window filter

        Map<String, String[]> parameterMap = request.getParameterMap();
        boolean hasBranchFilter = parameterMap.containsKey(FilterUtil.FilterKey.BRANCH.getValue());
        Filter deviceFilter;
        if (hasBranchFilter) {
            deviceFilter =
                    FilterUtil.FilterKey.BRANCH.getFilterForString(
                            FilterUtil.getFirstParameter(
                                    parameterMap, FilterUtil.FilterKey.BRANCH.getValue()));
        } else {
            deviceFilter =
                    FilterUtil.FilterKey.BRANCH.getFilterForString(ProfilingPointSummaryEntity.ALL);
        }

        boolean hasTargetFilter = parameterMap.containsKey(FilterUtil.FilterKey.TARGET.getValue());
        if (hasTargetFilter) {
            deviceFilter =
                    Query.CompositeFilterOperator.and(
                            deviceFilter,
                            FilterUtil.FilterKey.TARGET.getFilterForString(
                                    FilterUtil.getFirstParameter(
                                            parameterMap, FilterUtil.FilterKey.TARGET.getValue())));
        } else {
            deviceFilter =
                    Query.CompositeFilterOperator.and(
                            deviceFilter,
                            FilterUtil.FilterKey.TARGET.getFilterForString(
                                    ProfilingPointSummaryEntity.ALL));
        }

        Filter startFilter =
                new Query.FilterPredicate(
                        ProfilingPointSummaryEntity.START_TIME,
                        Query.FilterOperator.GREATER_THAN_OR_EQUAL,
                        startTime);
        Filter endFilter =
                new Query.FilterPredicate(
                        ProfilingPointSummaryEntity.START_TIME,
                        Query.FilterOperator.LESS_THAN_OR_EQUAL,
                        endTime);
        Filter timeFilter = Query.CompositeFilterOperator.and(startFilter, endFilter);

        Filter filter = Query.CompositeFilterOperator.and(timeFilter, deviceFilter);

        Query profilingPointQuery =
                new Query(ProfilingPointEntity.KIND)
                        .setFilter(
                                new Query.FilterPredicate(
                                        ProfilingPointEntity.TEST_NAME,
                                        Query.FilterOperator.EQUAL,
                                        testName));

        List<ProfilingPointEntity> profilingPoints = new ArrayList<>();
        for (Entity e :
                datastore
                        .prepare(profilingPointQuery)
                        .asIterable(DatastoreHelper.getLargeBatchOptions())) {
            ProfilingPointEntity pp = ProfilingPointEntity.fromEntity(e);
            if (pp == null) continue;
            profilingPoints.add(pp);
        }

        Map<ProfilingPointEntity, Iterable<Entity>> asyncEntities = new HashMap<>();
        for (ProfilingPointEntity pp : profilingPoints) {
            Query profilingQuery =
                    new Query(ProfilingPointSummaryEntity.KIND)
                            .setAncestor(pp.getKey())
                            .setFilter(filter);
            asyncEntities.put(
                    pp,
                    datastore
                            .prepare(profilingQuery)
                            .asIterable(DatastoreHelper.getLargeBatchOptions()));
        }

        Map<String, BoxPlot> plotMap = new HashMap<>();
        for (ProfilingPointEntity pp : profilingPoints) {
            if (!plotMap.containsKey(pp.getProfilingPointName())) {
                plotMap.put(
                        pp.getProfilingPointName(), new BoxPlot(pp.getProfilingPointName(), null, pp.getXLabel()));
            }
            BoxPlot plot = plotMap.get(pp.getProfilingPointName());
            Set<Long> timestamps = new HashSet<>();
            for (Entity e : asyncEntities.get(pp)) {
                ProfilingPointSummaryEntity pps = ProfilingPointSummaryEntity.fromEntity(e);
                if (pps == null) continue;
                plot.addSeriesData(Long.toString(pps.getStartTime()), pps.getSeries(), pps.getGlobalStats());
                timestamps.add(pps.getStartTime());
            }
            List<Long> timestampList = new ArrayList<>(timestamps);
            timestampList.sort(Comparator.reverseOrder());
            List<String> timestampStrings =
                    timestampList.stream().map(Object::toString).collect(Collectors.toList());
            plot.setLabels(timestampStrings);
        }

        List<BoxPlot> plots = new ArrayList<>();
        for (String key : plotMap.keySet()) {
            BoxPlot plot = plotMap.get(key);
            if (plot.size() == 0) continue;
            plots.add(plot);
        }
        plots.sort((b1, b2) -> b1.getName().compareTo(b2.getName()));

        Gson gson =
                new GsonBuilder()
                        .registerTypeHierarchyAdapter(BoxPlot.class, new GraphSerializer())
                        .create();

        FilterUtil.setAttributes(request, parameterMap);
        request.setAttribute("plots", gson.toJson(plots));
        request.setAttribute("testName", request.getParameter("testName"));
        request.setAttribute("branches", new Gson().toJson(DatastoreHelper.getAllBranches()));
        request.setAttribute("devices", new Gson().toJson(DatastoreHelper.getAllBuildFlavors()));
        dispatcher = request.getRequestDispatcher(PROFILING_OVERVIEW_JSP);
        try {
            dispatcher.forward(request, response);
        } catch (ServletException e) {
            logger.log(Level.SEVERE, "Servlet Exception caught : ", e);
        }
    }
}
