/*
 * Copyright 2018 Scytl Secure Electronic Voting SA
 *
 * All rights reserved
 *
 * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
 */
package com.scytl.products.ov.sdm.application.service;

import com.fasterxml.jackson.databind.ObjectReader;
import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;

import com.scytl.products.ov.sdm.domain.model.sdmconfig.I18nConfiguration;
import com.scytl.products.ov.sdm.domain.model.sdmconfig.Language;
import static com.scytl.products.ov.sdm.domain.service.ConfigConstants.SDM_DIR_NAME;
import static com.scytl.products.ov.sdm.domain.service.ConfigConstants.SDM_LANGS_DIR_NAME;

@Service
public class SdmConfigService {

    private static final Logger LOGGER = LoggerFactory.getLogger(AdminBoardService.class);
    private static final String GENERATE_PRE_VOTING_OUTPUTS_ENABLED_PROPERTY="generatePreVotingOutputsEnabled";
    private static final String GENERATE_POST_VOTING_OUTPUTS_ENABLED_PROPERTY="generatePostVotingOutputsEnabled";
    private static final String VC_GENERATION_PREPARATION_ENABLED_PROPERTY="vcGenerationPreparationEnabled";
    private static final String VC_PRECOMPUTATION_ENABLED_PROPERTY="vcPrecomputationEnabled";
    private static final String VC_COMPUTATION_ENABLED_PROPERTY="vcComputationEnabled";
    private static final String VC_DOWNLOAD_ENABLED_PROPERTY="vcDownloadEnabled";
    private static final String DOWNLOAD_PARTICIPATION_ENABLED_PROPERTY="downloadParticipationEnabled";
    private static final String IMPORT_EXPORT_ENABLED_PROPERTY="importExportEnabled";
    private static final String TALLYING_ENABLED_PROPERTY="tallyingEnabled";
    private static final String I18N_PROPERTY = "i18n";
    public static final String DEFAULT_LOCALE_FILE_NAME = "default";

    @Value("${generatePreVotingOutputs.enabled}")
    private boolean isGeneratePreVotingOutputsEnabled;
    
    @Value("${generatePostVotingOutputs.enabled}")
    private boolean isGeneratePostVotingOutputsEnabled;
    
    @Value("${vcGenerationPreparation.enabled}")
    private boolean isVcGenerationPreparationEnabled;
    
    @Value("${vcPrecomputation.enabled}")
    private boolean isVcPrecomputationEnabled;
    
    @Value("${vcComputation.enabled}")
    private boolean isVcComputationEnabled;
    
    @Value("${vcDownload.enabled}")
    private boolean isVcDownloadEnabled;
    
    @Value("${downloadParticipation.enabled}")
    private boolean isDownloadParticipationEnabled;
    
    @Value("${importExport.enabled}")
    private boolean isImportExportEnabled;
    
    @Value("${tallying.enabled}")
    private boolean isTallyingEnabled;

    @Value("${user.home}")
    private String workspace;

    @Autowired
    ObjectReader jsonReader;
    
    private Map<String, Object> config = new HashMap<>();

    public Map<String, Object> getConfig() throws IOException {
        config.put(GENERATE_PRE_VOTING_OUTPUTS_ENABLED_PROPERTY, isGeneratePreVotingOutputsEnabled);
        config.put(GENERATE_POST_VOTING_OUTPUTS_ENABLED_PROPERTY, isGeneratePostVotingOutputsEnabled);
        config.put(VC_GENERATION_PREPARATION_ENABLED_PROPERTY, isVcGenerationPreparationEnabled);
        config.put(VC_PRECOMPUTATION_ENABLED_PROPERTY, isVcPrecomputationEnabled);
        config.put(VC_COMPUTATION_ENABLED_PROPERTY, isVcComputationEnabled);
        config.put(VC_DOWNLOAD_ENABLED_PROPERTY, isVcDownloadEnabled);
        config.put(DOWNLOAD_PARTICIPATION_ENABLED_PROPERTY, isDownloadParticipationEnabled);
        config.put(IMPORT_EXPORT_ENABLED_PROPERTY, isImportExportEnabled);
        config.put(TALLYING_ENABLED_PROPERTY, isTallyingEnabled);
        addLanguagesConfiguration(config);

        return config;
    }

    public FileSystemResource loadLanguage(final String languageCode) {
        
        String languageFileName = languageCode;
        if (isDefaultLanguageCode(languageCode)) {
            languageFileName = DEFAULT_LOCALE_FILE_NAME;
        }
        
        final Path languageFilePath = Paths.get(workspace, SDM_DIR_NAME, SDM_LANGS_DIR_NAME, languageFileName + ".json");
        return new FileSystemResource(languageFilePath.toFile());
    }

    private boolean isDefaultLanguageCode(String languageCode) {
        I18nConfiguration i18nConfiguration = (I18nConfiguration)config.get(I18N_PROPERTY);
        return languageCode.equals(i18nConfiguration.getDefaultLanguage());
    }

    /**
     * Get list of languages discovered by checking what language files exist in the langs directory
     * It expects the language files to be named with the locale(.toString()) code + json extension. For each 'valid' file
     * it adds an entry to the result.
     * For the default language, it expects the file to be named "default.json". Since each file is a json with a top-element
     * named after the locale , we extract the 'real' locale from it.
     * @return list of languages [{"code": "locale1", "name": "name1"}, {...}...]
     * @param config
     */
    private void addLanguagesConfiguration(final Map<String, Object> config) throws IOException {

        I18nConfiguration i18nConfiguration = new I18nConfiguration();
        List<String> languageCodes = discoverAvailableLanguages();
        for(String languageCode : languageCodes) {
            String localeName = extractLocaleNameFromFile(languageCode);
            Language lang = processLocaleName(localeName);
            //was it the 'default'? should only be one...
            boolean isDefault = languageCode.equalsIgnoreCase(DEFAULT_LOCALE_FILE_NAME);
            i18nConfiguration.addLanguage(lang, isDefault);
        }
        config.put(I18N_PROPERTY, i18nConfiguration);
    }

    private Language processLocaleName(final String localeName) {
        String languageTag = localeName.replace('_','-');
        Locale locale = Locale.forLanguageTag(languageTag);
        return new Language(locale.toString(), locale.getDisplayLanguage(locale));
    }

    private String extractLocaleNameFromFile(final String localeFileName) throws IOException {
        final Path localeFilePath =
            Paths.get(workspace, SDM_DIR_NAME, SDM_LANGS_DIR_NAME, localeFileName + ".json");
        try(InputStream is = Files.newInputStream(localeFilePath)) {
            //get the first element 'name' from the json file. MUST be the name of a locale
            return jsonReader.readTree(is).fieldNames().next();
        }
    }

    private List<String> discoverAvailableLanguages() {
        final Path languagesPath = Paths.get(workspace, SDM_DIR_NAME, SDM_LANGS_DIR_NAME);
        try {
            return Files.list(languagesPath)
                .map(p -> p.toFile().getName())
                .filter(this::isValidNameOrDefault)
                .map(FilenameUtils::getBaseName)
                .collect(Collectors.toList());
        } catch (IOException e) {
        	LOGGER.error("Error trying to discover available languages.", e);
        }
        return Collections.emptyList();
    }

    private boolean isValidNameOrDefault(final String fileName) {
        //is the lang filename called "default" or is named after a 'valid' locale
        return "json".equalsIgnoreCase(FilenameUtils.getExtension(fileName)) 
        		&& (DEFAULT_LOCALE_FILE_NAME.equalsIgnoreCase(FilenameUtils.getBaseName(fileName)) ||
                Locale.forLanguageTag(FilenameUtils.getBaseName(fileName).replace('_','-')) != null);        		
    }
}
