package ru.yandex.canvas.service;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.text.Collator;
import java.util.AbstractMap.SimpleEntry;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.Resources;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ru.yandex.canvas.model.direct.Privileges;
import ru.yandex.canvas.model.stock.StockCategory;
import ru.yandex.canvas.model.stock.StockFile;
import ru.yandex.canvas.model.stock.StockFiles;

import static java.util.Collections.emptyList;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.mapping;
import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
import static org.springframework.context.i18n.LocaleContextHolder.getLocale;

/**
 * Provides stock images and their categories.
 *
 * @author pupssman
 */
public class StockService {

    private static final Logger logger = LoggerFactory.getLogger(StockService.class);

    private static final String CATEGORIES_FILE_PATH = "stock/categories.json";
    private static final String FILES_FILE_PATH = "stock/files.json";

    private final List<StockCategory> categories;
    private final List<StockFile> files;
    private final Map<String, StockFile> filesById;
    private final Map<Integer, List<StockFile>> filesByCategories;

    private final AuthService authService;

    public StockService(final ObjectMapper objectMapper, final AuthService authService) {
        this.authService = authService;
        try {
            categories = objectMapper.readValue(Resources.toString(Resources.getResource(CATEGORIES_FILE_PATH),
                    StandardCharsets.UTF_8), new TypeReference<List<StockCategory>>() {
            });

            files = objectMapper.readValue(Resources.toString(Resources.getResource(FILES_FILE_PATH),
                    StandardCharsets.UTF_8), new TypeReference<List<StockFile>>() {
            });

            filesByCategories = files.stream()
                    .flatMap(f -> f.getCategories().stream().map(c -> new SimpleEntry<>(c, f)))
                    .collect(groupingBy(SimpleEntry::getKey, mapping(Map.Entry::getValue, toList())));

            filesById = files.stream().collect(toMap(StockFile::getId, identity()));
        } catch (IOException e) {
            logger.error("can not load stock data", e);
            throw new RuntimeException("can not load stock data", e);
        }
    }

    public List<StockCategory> getCategories(final String query) {
        authService.requirePermission(Privileges.Permission.CREATIVE_GET);
        final Locale locale = getLocale();
        final Collator collator = Collator.getInstance(locale);
        return categories.stream()
                .filter(c -> filesByCategories.containsKey(c.getId()))  // do not return empty categories
                .filter(c -> c.getName().toLowerCase(locale).contains(query.toLowerCase(locale)))
                .sorted((c1, c2) -> collator.compare(c1.getName(), c2.getName()))  // sort by name wit locale
                .collect(toList());
    }

    public StockFiles getStockFiles(int limit, int offset) {
        authService.requirePermission(Privileges.Permission.CREATIVE_GET);
        return new StockFiles(files.size(), files.stream().skip(offset).limit(limit).collect(toList()));
    }

    /**
     * @return all the stock files that have at least one of those categories
     */
    public StockFiles getStockFiles(List<Integer> categoryIds, int limit, int offset) {
        authService.requirePermission(Privileges.Permission.CREATIVE_GET);

        // first collect to set to remove duplicates possible by one StockFile having multiple categories
        Set<StockFile> files = categoryIds.stream()
                .flatMap(cid -> filesByCategories.getOrDefault(cid, emptyList()).stream())
                .collect(toCollection(LinkedHashSet::new));  // LinkedHS to preserve order

        return new StockFiles(files.size(), files.stream().skip(offset).limit(limit).collect(toList()));
    }

    public Optional<StockFile> getFile(final String fileId) {

        authService.requirePermission(Privileges.Permission.CREATIVE_GET);

        return Optional.ofNullable(filesById.get(fileId));
    }

    public Optional<StockCategory> getCategory(final int id) {
        authService.requirePermission(Privileges.Permission.CREATIVE_GET);

        return categories.stream().filter(c -> c.getId() == id).findFirst();
    }
}
