package com.infoclinika.pdx.controller;

import com.infoclinika.pdx.projection.IdAndNameDto;
import com.infoclinika.pdx.projection.MutationSummary;
import com.infoclinika.pdx.projection.mutation.MutationProjection;
import com.infoclinika.pdx.projection.mutation.MutationRateDto;
import com.infoclinika.pdx.projection.mutation.SampleTypeWithMutations;
import com.infoclinika.pdx.service.annotation.PrimarySiteService;
import com.infoclinika.pdx.service.exception.ApplicationException;
import com.infoclinika.pdx.service.mutation.MutationService;
import com.infoclinika.pdx.specification.FilterQuery;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import java.util.Optional;

import static java.util.stream.Collectors.toList;
import static org.springframework.http.HttpHeaders.CONTENT_DISPOSITION;
import static org.springframework.http.HttpHeaders.CONTENT_TYPE;

/**
 * Handles requests for Mutation data.
 */
@RestController
@RequestMapping("/api/mutation")
public class MutationController {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private static final String ATTACHMENT_NAME = "attachment;filename=mutations.tsv";
    private static final String CHARACTER_ENCODING = "UTF-8";
    private static final String CONTENT_TYPE_VALUE = "application/csv";
    private static final String SAMPLE_NAME_PATH = "mutationCall.sample.name";
    private static final String SAMPLE_SORTING_PROPERTY = "sample.name";

    private static final String TAB_DELIMITER = "\t";
    private static final String LINE_SEPARATOR = "\n";

    private static final String[] MUTATION_HEADERS = {
            "GeneSymbol", "NCBI_Build", "Chromosome", "Start_position", "End_position",
            "Strand", "Variant_Classification", "Variant_Type", "Reference_Allele", "Tumor_Seq_Allele1",
            "dbSNP_RS", "Genome_Change", "Annotation_Transcript", "Entrez_Gene_Id", "Tumor_Sample_Barcode",
            "cDNA_Change", "Protein_Change", "Allele_Freq", "Read_Depth", "COSMIC_ID"
    };
    private static final String FILE_HEADER = String.join(TAB_DELIMITER, MUTATION_HEADERS)
                                                    .concat(LINE_SEPARATOR);

    private final MutationService mutationService;
    private final PrimarySiteService primarySiteService;


    /**
     * Constructor to provide required services.
     */
    @Autowired
    public MutationController(MutationService service, PrimarySiteService primarySiteService) {
        this.mutationService = service;
        this.primarySiteService = primarySiteService;
    }

    /**
     * Returns list of Mutations.
     */
    @GetMapping("/list")
    @ResponseStatus(HttpStatus.OK)
    public Page<MutationSummary> getMutations(
            @RequestParam(name = "queries", required = false) List<FilterQuery> queries,
            Pageable pageable) {
        final int pageNumber = pageable.getPageNumber();
        final int pageSize = pageable.getPageSize();

        logger.debug("Request to /api/mutation/list?queries={}&page={}&size={}", queries, pageNumber, pageSize);

        final List<FilterQuery> filterQueries = primarySiteService.getQueriesWithIgnoredCategories(queries);
        return mutationService.getMutations(filterQueries, getPageable(pageable));
    }

    /**
     * Returns list of Mutations.
     */
    @GetMapping("/download")
    @ResponseStatus(HttpStatus.OK)
    public void downloadMutations(
            @RequestParam(name = "queries", required = false) List<FilterQuery> queries,
            HttpServletResponse response) throws ApplicationException {
        logger.debug("Request to /api/mutation/download?queries={}", queries);

        final List<FilterQuery> filterQueries = primarySiteService.getQueriesWithIgnoredCategories(queries);
        final List<MutationProjection> mutations = mutationService.getMutations(filterQueries);

        response.addHeader(CONTENT_TYPE, CONTENT_TYPE_VALUE);
        response.addHeader(CONTENT_DISPOSITION, ATTACHMENT_NAME);
        response.setCharacterEncoding(CHARACTER_ENCODING);

        try (PrintWriter out = response.getWriter()) {
            out.write(FILE_HEADER);
            mutations.forEach(mutation -> out.write(mutation.toTsvString(TAB_DELIMITER, LINE_SEPARATOR)));
            out.flush();
        } catch (IOException e) {
            logger.error("Exception occurred: " + e.getMessage(), e);
            throw new ApplicationException(e.getMessage());
        }
    }

    /**
     * Returns summary for Mutation frequency rate.
     */
    @GetMapping("/summary")
    @ResponseStatus(HttpStatus.OK)
    public List<MutationRateDto> getMutationSummary(
            @RequestParam(name = "queries", required = false) List<FilterQuery> queries) {
        logger.debug("Request to /api/mutation/summary?queries={}", queries);
        final List<FilterQuery> filterQueries = primarySiteService.getQueriesWithIgnoredCategories(queries);
        return mutationService.getMutationSummary(filterQueries);
    }

    /**
     * Returns data for Mutation data grouped by Sample types.
     */
    @GetMapping("/data")
    @ResponseStatus(HttpStatus.OK)
    public List<SampleTypeWithMutations> getMutationData(
            @RequestParam(name = "queries", required = false) List<FilterQuery> queries) {
        logger.debug("Request to /api/mutation/data?queries={}", queries);
        final List<FilterQuery> filterQueries = primarySiteService.getQueriesWithIgnoredCategories(queries);
        return mutationService.getMutationData(filterQueries);
    }

    private Pageable getPageable(Pageable pageable) {
        final int pageNumber = pageable.getPageNumber();
        final int pageSize = pageable.getPageSize();
        final List<Order> orders = pageable.getSort()
                                           .stream()
                                           .collect(toList());
        final Optional<Order> optionalOrder = orders.stream()
                                                    .filter(order -> order.getProperty()
                                                                          .equals(SAMPLE_SORTING_PROPERTY))
                                                    .findFirst();
        if (!optionalOrder.isPresent()) {
            return pageable;
        }

        final Order order = optionalOrder.get();
        final int index = orders.indexOf(order);
        orders.set(index, new Order(order.getDirection(), SAMPLE_NAME_PATH));

        return PageRequest.of(pageNumber, pageSize, Sort.by(orders));
    }

    /**
     * Returns list of primary sites with mutation data.
     */
    @GetMapping("/primary-site/list")
    @ResponseStatus(HttpStatus.OK)
    public List<IdAndNameDto> getPrimarySites(
            @RequestParam(name = "queries", required = false) List<FilterQuery> queries) {
        logger.debug("Request to /api/mutation/primary-site/list?queries={}", queries);

        final List<FilterQuery> filterQueries = primarySiteService.getQueriesWithIgnoredCategories(queries);
        return mutationService.getPrimarySites(filterQueries);
    }
}
