/*
 * 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.coa.batch.dataaccess.impl;

import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.coa.businessobject.AccountingPeriod;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.batch.dataaccess.impl.FiscalYearMakerImpl;
import org.kuali.kfs.sys.businessobject.FiscalYearBasedBusinessObject;

import java.sql.Date;
import java.util.Collection;
import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Performs custom population of accounting periods records for a new year being created in the fiscal year maker process
 */
public class AccountingPeriodFiscalYearMakerImpl extends FiscalYearMakerImpl {

    public AccountingPeriodFiscalYearMakerImpl() {
        super();

        super.setAllowOverrideTargetYear(false);
    }

    /**
     * Updates the year on the fiscal period name and sets status to open for next year records
     *
     * @see org.kuali.kfs.coa.batch.dataaccess.impl.FiscalYearMakerHelperImpl#changeForNewYear(java.lang.Integer,
     * org.kuali.rice.krad.bo.PersistableBusinessObject)
     */
    @Override
    public void changeForNewYear(Integer baseFiscalYear, FiscalYearBasedBusinessObject currentRecord) {
        super.changeForNewYear(baseFiscalYear, currentRecord);

        AccountingPeriod accountingPeriod = (AccountingPeriod) currentRecord;

        // update fiscal period name which contains the fiscal year
        String fiscalPeriodName = accountingPeriod.getUniversityFiscalPeriodName();

        String oldCalendarStartYear = new Integer(accountingPeriod.getUniversityFiscalYear() - 2).toString();
        String oldCalendarEndYear = new Integer(accountingPeriod.getUniversityFiscalYear() - 1).toString();

        String newCalendarStartYear = new Integer(accountingPeriod.getUniversityFiscalYear() - 1).toString();
        String newCalendarEndYear = new Integer(accountingPeriod.getUniversityFiscalYear()).toString();

        // replace 4 digit year in name if found, else replace 2 digit
        if (StringUtils.contains(fiscalPeriodName, oldCalendarEndYear)) {
            fiscalPeriodName = StringUtils.replace(fiscalPeriodName, oldCalendarEndYear, newCalendarEndYear);
        } else if (StringUtils.contains(fiscalPeriodName, oldCalendarStartYear)) {
            fiscalPeriodName = StringUtils.replace(fiscalPeriodName, oldCalendarStartYear, newCalendarStartYear);
        } else {
            fiscalPeriodName = updateTwoDigitYear(newCalendarEndYear.substring(2, 4), oldCalendarEndYear.substring(2, 4), fiscalPeriodName);
            fiscalPeriodName = updateTwoDigitYear(newCalendarStartYear.substring(2, 4), oldCalendarStartYear.substring(2, 4), fiscalPeriodName);
        }

        accountingPeriod.setUniversityFiscalPeriodName(fiscalPeriodName);

        // increment period end date by one year
        accountingPeriod.setUniversityFiscalPeriodEndDate(addYearToDate(accountingPeriod.getUniversityFiscalPeriodEndDate()));

        // set status to closed
        accountingPeriod.setActive(false);
    }

    /**
     * Retrieves all Accounting Period records for the first copied fiscal year and make active
     *
     * @see org.kuali.kfs.coa.batch.dataaccess.impl.FiscalYearMakerHelperImpl#performCustomProcessing(java.lang.Integer)
     */
    @Override
    public void performCustomProcessing(Integer baseFiscalYear, boolean firstCopyYear) {
        if (!firstCopyYear) {
            return;
        }
        Collection<AccountingPeriod> accountingPeriods = businessObjectService.findMatching(AccountingPeriod.class, Collections.singletonMap(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, baseFiscalYear + 1));
        for (AccountingPeriod accountingPeriod : accountingPeriods) {
            accountingPeriod.setActive(true);
            businessObjectService.save(accountingPeriod);
        }
    }

    /**
     * Adds one year to the given date
     *
     * @param inDate date to increment
     * @return Date incoming date plus one year
     */
    protected java.sql.Date addYearToDate(Date inDate) {
        GregorianCalendar currentCalendarDate = new GregorianCalendar();
        currentCalendarDate.clear();

        currentCalendarDate.setTimeInMillis(inDate.getTime());
        currentCalendarDate.add(GregorianCalendar.YEAR, 1);

        return new Date(currentCalendarDate.getTimeInMillis());
    }

    /**
     * this routine is provided to update string fields which contain two-digit years that need to be updated for display. it is
     * very specific, but it's necessary. "two-digit year" means the two numeric characters preceded by a non-numeric character.
     *
     * @param newYear
     * @param oldYear
     * @param currentString
     * @return the updated string for a two digit year
     */
    protected String updateTwoDigitYear(String newYear, String oldYear, String currentString) {
        // group 1 is the bounded by the outermost set of parentheses
        // group 2 is the first inner set
        // group 3 is the second inner set--a two-digit year at the beginning of the line
        String regExpString = "(([^0-9]{1}" + oldYear + ")|^(" + oldYear + "))";
        Pattern pattern = Pattern.compile(regExpString);
        Matcher matcher = pattern.matcher(currentString);

        // start looking for a match
        boolean matched = matcher.find();
        if (!matched) {
            // just return if nothing is found
            return currentString;
        }

        // we found something
        // we have to process it
        String returnString = currentString;
        StringBuffer outString = new StringBuffer();
        // is there a match at the beginning of the line (a match with group 3)?
        if (matcher.group(3) != null) {
            // there is a two-digit-year string at the beginning of the line
            // we want to replace it
            matcher.appendReplacement(outString, newYear);
            // find the next match if there is one
            matched = matcher.find();
        }

        while (matched) {
            // the new string will no longer match with group 3
            // if there is still a match, it will be with group 2
            // now we have to prefix the new year string with the same
            // non-numeric character as the next match (hyphen, space, whatever)
            String newYearString = matcher.group(2).substring(0, 1) + newYear;
            matcher.appendReplacement(outString, newYearString);
            matched = matcher.find();
        }

        // dump whatever detritus is left into the new string
        matcher.appendTail(outString);

        return outString.toString();
    }

}
