/*
 * The Kuali Financial System, a comprehensive financial management system for higher education.
 *
 * Copyright 2005-2016 The Kuali Foundation
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.kuali.kfs.module.cg.service.impl;

import au.com.bytecode.opencsv.CSVReader;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;
import org.kuali.kfs.coreservice.framework.parameter.ParameterService;
import org.kuali.kfs.krad.service.BusinessObjectService;
import org.kuali.kfs.module.cg.batch.CfdaBatchStep;
import org.kuali.kfs.module.cg.businessobject.CFDA;
import org.kuali.kfs.module.cg.businessobject.CfdaUpdateResults;
import org.kuali.kfs.module.cg.service.CfdaService;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.service.NonTransactional;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.springframework.transaction.annotation.Transactional;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Calendar;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

public class CfdaServiceImpl implements CfdaService {
    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CfdaServiceImpl.class);

    protected BusinessObjectService businessObjectService;
    protected static Comparator cfdaComparator;
    private DateTimeService dateTimeService;
    protected ParameterService parameterService;

    static {
        cfdaComparator = new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                String lhs = (String) o1;
                String rhs = (String) o2;
                return lhs.compareTo(rhs);
            }
        };
    }

    @NonTransactional
    public void setDateTimeService(DateTimeService dateTimeService) {
        this.dateTimeService = dateTimeService;
    }

    /**
     * Sets the parameterService attribute value.
     *
     * @param parameterService The parameterService to set.
     */
    public void setParameterService(ParameterService parameterService) {
        this.parameterService = parameterService;
    }

    /**
     * @return
     * @throws IOException
     */
    public SortedMap<String, CFDA> getGovCodes() throws IOException {
        Calendar calendar = dateTimeService.getCurrentCalendar();
        SortedMap<String, CFDA> govMap = new TreeMap<String, CFDA>();

        // ftp://ftp.cfda.gov/programs09187.csv
        String govURL = parameterService.getParameterValueAsString(CfdaBatchStep.class, KFSConstants.SOURCE_URL_PARAMETER);
        String fileName = StringUtils.substringAfterLast(govURL, "/");
        govURL = StringUtils.substringBeforeLast(govURL, "/");
        if (StringUtils.contains(govURL, "ftp://")) {
            govURL = StringUtils.remove(govURL, "ftp://");
        }

        // need to pull off the '20' in 2009
        String year = "" + calendar.get(Calendar.YEAR);
        year = year.substring(2, 4);
        fileName = fileName + year;

        // the last 3 numbers in the file name are the day of the year, but the files are from "yesterday"
        fileName = fileName + String.format("%03d", calendar.get(Calendar.DAY_OF_YEAR) - 1);
        fileName = fileName + ".csv";

        LOG.info("Getting government file: " + fileName + " for update");

        InputStream inputStream = null;
        FTPClient ftp = new FTPClient();
        try {
            ftp.connect(govURL);
            int reply = ftp.getReplyCode();

            if (!FTPReply.isPositiveCompletion(reply)) {
                LOG.error("FTP connection to server not established.");
                throw new IOException("FTP connection to server not established.");
            }

            boolean isLoggedIn = ftp.login("anonymous", "");
            if (!isLoggedIn) {
                LOG.error("Could not login as anonymous.");
                throw new IOException("Could not login as anonymous.");
            }

            LOG.info("Successfully connected and logged in");
            ftp.enterLocalPassiveMode();
            inputStream = ftp.retrieveFileStream(fileName);
            if (inputStream != null) {
                LOG.info("reading input stream");
                InputStreamReader screenReader = new InputStreamReader(inputStream);
                BufferedReader screen = new BufferedReader(screenReader);

                CSVReader csvReader = new CSVReader(screenReader, ',', '"', 1);
                List<String[]> lines = csvReader.readAll();
                for (String[] line : lines) {
                    String title = line[0];
                    String number = line[1];

                    CFDA cfda = new CFDA();
                    cfda.setCfdaNumber(number);
                    cfda.setCfdaProgramTitleName(title);

                    govMap.put(number, cfda);
                }
            }

            ftp.logout();
            ftp.disconnect();
        } finally {
            if (ftp.isConnected()) {
                ftp.disconnect();
            }
        }

        return govMap;
    }

    /**
     * @return
     * @throws IOException
     */
    public SortedMap<String, CFDA> getKfsCodes() throws IOException {
        Collection allCodes = businessObjectService.findAll(CFDA.class);

        SortedMap<String, CFDA> kfsMapAll = new TreeMap<String, CFDA>(cfdaComparator);
        for (Object o : allCodes) {
            CFDA c = (CFDA) o;
            kfsMapAll.put(c.getCfdaNumber(), c);
        }
        return kfsMapAll;
    }

    /**
     * @see org.kuali.kfs.module.cg.service.CfdaService#update()
     */
    @Transactional
    @Override
    public CfdaUpdateResults update() throws IOException {

        CfdaUpdateResults results = new CfdaUpdateResults();
        Map<String, CFDA> govMap = null;

        try {
            govMap = getGovCodes();
        } catch (IOException ioe) {
            LOG.error("Error connecting to URL resource: " + ioe.getMessage(), ioe);
            StringBuilder builder = new StringBuilder();
            builder.append("No updates took place.\n");
            builder.append(ioe.getMessage());
            results.setMessage(builder.toString());
            return results;
        }
        Map<String, CFDA> kfsMap = getKfsCodes();

        results.setNumberOfRecordsInKfsDatabase(kfsMap.keySet().size());
        results.setNumberOfRecordsRetrievedFromWebSite(govMap.keySet().size());

        for (Object key : kfsMap.keySet()) {

            CFDA cfdaKfs = kfsMap.get(key);
            CFDA cfdaGov = govMap.get(key);

            if (cfdaKfs.getCfdaMaintenanceTypeId().startsWith("M")) {
                // Leave it alone. It's maintained manually.
                results.setNumberOfRecordsNotUpdatedBecauseManual(1 + results.getNumberOfRecordsNotUpdatedBecauseManual());
            } else if (cfdaKfs.getCfdaMaintenanceTypeId().startsWith("A")) {

                if (cfdaGov == null) {
                    if (cfdaKfs.isActive()) {
                        cfdaKfs.setActive(false);
                        businessObjectService.save(cfdaKfs);
                        results.setNumberOfRecordsDeactivatedBecauseNoLongerOnWebSite(results.getNumberOfRecordsDeactivatedBecauseNoLongerOnWebSite() + 1);
                    } else {
                        // Leave it alone for historical purposes
                        results.setNumberOfRecrodsNotUpdatedForHistoricalPurposes(results.getNumberOfRecrodsNotUpdatedForHistoricalPurposes() + 1);
                    }
                } else {
                    if (cfdaKfs.isActive()) {
                        results.setNumberOfRecordsUpdatedBecauseAutomatic(results.getNumberOfRecordsUpdatedBecauseAutomatic() + 1);
                    } else {
                        cfdaKfs.setActive(true);
                        results.setNumberOfRecordsReActivated(results.getNumberOfRecordsReActivated() + 1);
                    }

                    cfdaKfs.setCfdaProgramTitleName(cfdaGov.getCfdaProgramTitleName());
                    businessObjectService.save(cfdaKfs);
                }
            }

            // Remove it from the govMap so we know what codes from the govMap don't already exist in KFS.
            govMap.remove(key);
        }

        // What's left in govMap now is just the codes that don't exist in KFS
        for (String key : govMap.keySet()) {
            CFDA cfdaGov = govMap.get(key);
            cfdaGov.setCfdaMaintenanceTypeId("AUTOMATIC");
            cfdaGov.setActive(true);
            businessObjectService.save(cfdaGov);
            results.setNumberOfRecordsNewlyAddedFromWebSite(results.getNumberOfRecordsNewlyAddedFromWebSite() + 1);
        }

        return results;
    }

    public void setBusinessObjectService(BusinessObjectService businessObjectService) {
        this.businessObjectService = businessObjectService;
    }

    @Override
    public CFDA getByPrimaryId(String cfdaNumber) {
        if (StringUtils.isBlank(cfdaNumber)) {
            return null;
        }
        return businessObjectService.findBySinglePrimaryKey(CFDA.class, cfdaNumber.trim());
    }

}
