<commit_before>/*
 * Copyright 2017-2019 <NAME>
 *
 * 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.github.lerocha.opencambio.service;

import com.github.lerocha.opencambio.dto.Rate;
import com.github.lerocha.opencambio.ecb.EcbClient;
import com.github.lerocha.opencambio.ecb.dto.CurrencyExchangeRate;
import com.github.lerocha.opencambio.ecb.dto.DailyExchangeRate;
import com.github.lerocha.opencambio.ecb.dto.ExchangeRatesResponse;
import com.github.lerocha.opencambio.entity.Currency;
import com.github.lerocha.opencambio.entity.ExchangeRate;
import com.github.lerocha.opencambio.repository.CurrencyRepository;
import com.github.lerocha.opencambio.repository.ExchangeRateRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import java.math.BigDecimal;
import java.sql.Date;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;

import static java.time.temporal.ChronoUnit.MONTHS;

/**
 * Created by lerocha on 2/1/17.
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class CurrencyServiceImpl implements CurrencyService {

    private static final Currency BASE_CURRENCY = new Currency("EUR", java.util.Currency.getInstance("EUR").getDisplayName(), LocalDate.parse("1999-01-04"), LocalDate.now());

    private final CurrencyRepository currencyRepository;
    private final ExchangeRateRepository exchangeRateRepository;
    private final EcbClient ecbClient;

    @Override
    @Cacheable(cacheNames = "currencies")
    public List<Currency> getCurrencies(Locale locale) {
        List<Currency> currencies = new ArrayList<>();
        List<Object[]> results = exchangeRateRepository.findAvailableCurrencies();
        for (Object[] result : results) {
            String currencyCode = (String) result[0];
            Currency currency = new Currency(currencyCode, java.util.Currency.getInstance(currencyCode).getDisplayName(locale != null ? locale : Locale.US));
            Date startDate = (Date) result[1];
            currency.setStartDate(startDate.toLocalDate());
            Date endDate = (Date) result[2];
            currency.setEndDate(endDate.toLocalDate());
            currencies.add(currency);
        }
        log.info("getCurrencies; locale={}; total={}", locale, currencies.size());
        return currencies.stream().sorted(Comparator.comparing(Currency::getCode)).collect(Collectors.toList());
    }

    @Override
    @Cacheable(cacheNames = "currencies")
    public Currency getCurrency(String code, Locale locale) {
        Currency currency = currencyRepository.findById(code).orElse(null);
        if (currency != null) {
            currency.setDisplayName(java.util.Currency.getInstance(code).getDisplayName(locale != null ? locale : Locale.US));
        }
        log.info("getCurrency; locale={}", locale);
        return currency;
    }

    @Override
    @Cacheable(cacheNames = "rates")
    public Page<Rate> getCurrencyRates(String code, LocalDate startDate, LocalDate endDate, Integer offset) {
        Assert.notNull(code, "currency code is required");

        if (startDate == null) {
            startDate = exchangeRateRepository.findMinExchangeDate();
        }
        if (endDate == null) {
            endDate = exchangeRateRepository.findMaxExchangeDate();
        }

        // Calculate month pagination.
        int months = (int) MONTHS.between(startDate, endDate) + 1;

        if (offset == null || offset > months) {
            offset = months  - 1;
        }

        // if not the first page, then adjust start as the first day of the month of this page.
        LocalDate pageStart = (offset > 0) ? startDate.plusMonths(offset).withDayOfMonth(1) : startDate;

        // if not the last page, then adjust end as the last day of the month of this page.
        LocalDate pageEnd = (offset < months - 1) ? pageStart.plusMonths(1).withDayOfMonth(1).minusDays(1) : endDate;

        // Get exchange rates from the database.
        List<ExchangeRate> allExchangeRates = exchangeRateRepository.findByExchangeDateBetweenOrderByExchangeDate(pageStart, pageEnd);

        // Group them by day and transform into a List of Rate objects.
        List<ExchangeRate> dailyExchangeRates = new ArrayList<>();
        List<Rate> rates = new ArrayList<>();
        LocalDate date = null;
        for (ExchangeRate exchangeRate : allExchangeRates) {
            if (date == null) {
                date = exchangeRate.getExchangeDate();
            } else if (!date.equals(exchangeRate.getExchangeDate())) {
                rates.add(new Rate(code, date, dailyExchangeRates));
                dailyExchangeRates.clear();
                date = exchangeRate.getExchangeDate();
            }
            dailyExchangeRates.add(exchangeRate);
        }
        if (dailyExchangeRates.size() > 0) {
            rates.add(new Rate(code, date, dailyExchangeRates));
        }

        Pageable pageable = PageRequest.of(offset, rates.size());
        Page<Rate> page = new PageImpl<>(rates, pageable, months * rates.size());
        log.info("getCurrencyRates; code={}; startDate={}; endDate={}; total={}; offset={}; totalPages={}",
                code, startDate, endDate, rates.size(), offset, page.getTotalPages());
        return page;
    }

    @Override
    @Cacheable(cacheNames = "rates")
    public Rate getCurrencyRatesByDate(String code, LocalDate date) {
        Assert.notNull(code, "currency code is required");
        Assert.notNull(date, "date is required");
        // Get the last 7 days in case requested date falls in a non business day.
        List<ExchangeRate> allExchangeRates = exchangeRateRepository.findByExchangeDateBetweenOrderByExchangeDate(date.minusDays(7), date);
        List<ExchangeRate> exchangeRates = new ArrayList<>();
        LocalDate availableDate = date;
        if (allExchangeRates.size() > 0) {
            // Get the latest date, which is the date of the last element of the ordered list.
            availableDate = allExchangeRates.get(allExchangeRates.size() - 1).getExchangeDate();
            // Loop through the ordered list on the reverse order to get the last records.
            for (int i = allExchangeRates.size() - 1; i >= 0; i--) {
                ExchangeRate exchangeRate = allExchangeRates.get(i);
                if (!exchangeRate.getExchangeDate().equals(availableDate)) {
                    break;
                }
                exchangeRates.add(exchangeRate);
            }
        }
        log.info("getCurrencyRatesByDate; code={}; requestedDate={}; availableDate={}", code, date, availableDate);
        return new Rate(code, availableDate, exchangeRates);
    }

    @Override
    @Cacheable(cacheNames = "rates")
    public Rate getLatestCurrencyRates(String code) {
        LocalDate date = exchangeRateRepository.findMaxExchangeDate();
        if (date == null) {
            return null;
        }
        return getCurrencyRatesByDate(code, date);
    }

    @Override
    @Transactional
    @CacheEvict(cacheNames = "rates", allEntries = true)
    public List<ExchangeRate> refreshExchangeRates() {
        LocalDate lastRefresh = exchangeRateRepository.findMaxExchangeDate();
        log.info("refreshExchangeRates; status=starting; lastRefresh={}", lastRefresh);

        // Initialize currency table.<commit_msg>fixing caching to not cache empty currency list<commit_after>/*
 * Copyright 2017-2019 <NAME>
 *
 * 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.github.lerocha.opencambio.service;

import com.github.lerocha.opencambio.dto.Rate;
import com.github.lerocha.opencambio.ecb.EcbClient;
import com.github.lerocha.opencambio.ecb.dto.CurrencyExchangeRate;
import com.github.lerocha.opencambio.ecb.dto.DailyExchangeRate;
import com.github.lerocha.opencambio.ecb.dto.ExchangeRatesResponse;
import com.github.lerocha.opencambio.entity.Currency;
import com.github.lerocha.opencambio.entity.ExchangeRate;
import com.github.lerocha.opencambio.repository.CurrencyRepository;
import com.github.lerocha.opencambio.repository.ExchangeRateRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import java.math.BigDecimal;
import java.sql.Date;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;

import static java.time.temporal.ChronoUnit.MONTHS;

/**
 * Created by lerocha on 2/1/17.
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class CurrencyServiceImpl implements CurrencyService {

    private static final Currency BASE_CURRENCY = new Currency("EUR", java.util.Currency.getInstance("EUR").getDisplayName(), LocalDate.parse("1999-01-04"), LocalDate.now());

    private final CurrencyRepository currencyRepository;
    private final ExchangeRateRepository exchangeRateRepository;
    private final EcbClient ecbClient;

    @Override
    @Cacheable(cacheNames = "currencies", unless = "#result.size()==0")
    public List<Currency> getCurrencies(Locale locale) {
        List<Currency> currencies = new ArrayList<>();
        List<Object[]> results = exchangeRateRepository.findAvailableCurrencies();
        for (Object[] result : results) {
            String currencyCode = (String) result[0];
            Currency currency = new Currency(currencyCode, java.util.Currency.getInstance(currencyCode).getDisplayName(locale != null ? locale : Locale.US));
            Date startDate = (Date) result[1];
            currency.setStartDate(startDate.toLocalDate());
            Date endDate = (Date) result[2];
            currency.setEndDate(endDate.toLocalDate());
            currencies.add(currency);
        }
        log.info("getCurrencies; locale={}; total={}", locale, currencies.size());
        return currencies.stream().sorted(Comparator.comparing(Currency::getCode)).collect(Collectors.toList());
    }

    @Override
    @Cacheable(cacheNames = "currencies")
    public Currency getCurrency(String code, Locale locale) {
        Currency currency = currencyRepository.findById(code).orElse(null);
        if (currency != null) {
            currency.setDisplayName(java.util.Currency.getInstance(code).getDisplayName(locale != null ? locale : Locale.US));
        }
        log.info("getCurrency; locale={}", locale);
        return currency;
    }

    @Override
    @Cacheable(cacheNames = "rates", unless = "#result.getTotalElements()==0")
    public Page<Rate> getCurrencyRates(String code, LocalDate startDate, LocalDate endDate, Integer offset) {
        Assert.notNull(code, "currency code is required");

        if (startDate == null) {
            startDate = exchangeRateRepository.findMinExchangeDate();
        }
        if (endDate == null) {
            endDate = exchangeRateRepository.findMaxExchangeDate();
        }

        // Calculate month pagination.
        int months = (int) MONTHS.between(startDate, endDate) + 1;

        if (offset == null || offset > months) {
            offset = months  - 1;
        }

        // if not the first page, then adjust start as the first day of the month of this page.
        LocalDate pageStart = (offset > 0) ? startDate.plusMonths(offset).withDayOfMonth(1) : startDate;

        // if not the last page, then adjust end as the last day of the month of this page.
        LocalDate pageEnd = (offset < months - 1) ? pageStart.plusMonths(1).withDayOfMonth(1).minusDays(1) : endDate;

        // Get exchange rates from the database.
        List<ExchangeRate> allExchangeRates = exchangeRateRepository.findByExchangeDateBetweenOrderByExchangeDate(pageStart, pageEnd);

        // Group them by day and transform into a List of Rate objects.
        List<ExchangeRate> dailyExchangeRates = new ArrayList<>();
        List<Rate> rates = new ArrayList<>();
        LocalDate date = null;
        for (ExchangeRate exchangeRate : allExchangeRates) {
            if (date == null) {
                date = exchangeRate.getExchangeDate();
            } else if (!date.equals(exchangeRate.getExchangeDate())) {
                rates.add(new Rate(code, date, dailyExchangeRates));
                dailyExchangeRates.clear();
                date = exchangeRate.getExchangeDate();
            }
            dailyExchangeRates.add(exchangeRate);
        }
        if (dailyExchangeRates.size() > 0) {
            rates.add(new Rate(code, date, dailyExchangeRates));
        }

        Pageable pageable = PageRequest.of(offset, rates.size());
        Page<Rate> page = new PageImpl<>(rates, pageable, months * rates.size());
        log.info("getCurrencyRates; code={}; startDate={}; endDate={}; total={}; offset={}; totalPages={}",
                code, startDate, endDate, rates.size(), offset, page.getTotalPages());
        return page;
    }

    @Override
    @Cacheable(cacheNames = "rates")
    public Rate getCurrencyRatesByDate(String code, LocalDate date) {
        Assert.notNull(code, "currency code is required");
        Assert.notNull(date, "date is required");
        // Get the last 7 days in case requested date falls in a non business day.
        List<ExchangeRate> allExchangeRates = exchangeRateRepository.findByExchangeDateBetweenOrderByExchangeDate(date.minusDays(7), date);
        List<ExchangeRate> exchangeRates = new ArrayList<>();
        LocalDate availableDate = date;
        if (allExchangeRates.size() > 0) {
            // Get the latest date, which is the date of the last element of the ordered list.
            availableDate = allExchangeRates.get(allExchangeRates.size() - 1).getExchangeDate();
            // Loop through the ordered list on the reverse order to get the last records.
            for (int i = allExchangeRates.size() - 1; i >= 0; i--) {
                ExchangeRate exchangeRate = allExchangeRates.get(i);
                if (!exchangeRate.getExchangeDate().equals(availableDate)) {
                    break;
                }
                exchangeRates.add(exchangeRate);
            }
        }
        log.info("getCurrencyRatesByDate; code={}; requestedDate={}; availableDate={}", code, date, availableDate);
        return new Rate(code, availableDate, exchangeRates);
    }

    @Override
    @Cacheable(cacheNames = "rates")
    public Rate getLatestCurrencyRates(String code) {
        LocalDate date = exchangeRateRepository.findMaxExchangeDate();
        if (date == null) {
            return null;
        }
        return getCurrencyRatesByDate(code, date);
    }

    @Override
    @Transactional
    @CacheEvict(cacheNames = {"rates", "currencies"}, allEntries = true)
    public List<ExchangeRate> refreshExchangeRates() {
        LocalDate lastRefresh = exchangeRateRepository.findMaxExchangeDate();
        log.info("refreshExchangeRates; status=starting; lastRefresh={}", lastRefresh);

        // Initialize currency table.
{'fraction_non_alphanumeric': 0.10654677950164551, 'fraction_numerical': 0.0054066760695815706, 'mean_word_length': 3.28316133903851, 'pattern_counts': {'":': 0, '<': 39, '<?xml version=': 0, '>': 47, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5631303', 'n_tokens_mistral': 4802, 'n_tokens_neox': 4569, 'n_words': 1167}