<?php
/*********************************************************************************
 *
 * TimeTrex is a Workforce Management program developed by
 * TimeTrex Software Inc. Copyright (C) 2003 - 2021 TimeTrex Software Inc.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by
 * the Free Software Foundation with the addition of the following permission
 * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
 * WORK IN WHICH THE COPYRIGHT IS OWNED BY TIMETREX, TIMETREX DISCLAIMS THE
 * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 *
 * 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 or write to the Free
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 *
 *
 * You can contact TimeTrex headquarters at Unit 22 - 2475 Dobbin Rd. Suite
 * #292 West Kelowna, BC V4T 2E9, Canada or at email address info@timetrex.com.
 *
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License version 3.
 *
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License
 * version 3, these Appropriate Legal Notices must retain the display of the
 * "Powered by TimeTrex" logo. If the display of the logo is not reasonably
 * feasible for technical reasons, the Appropriate Legal Notices must display
 * the words "Powered by TimeTrex".
 *
 ********************************************************************************/


/**
 * @package PayrollDeduction\US
 */
class PayrollDeduction_US extends PayrollDeduction_US_Data {
	//
	// Federal
	//
	function setFederalFilingStatus( $value ) {
		//Check for invalid value, default to single if found.
		if ( in_array( $value, [ 10, 20, 40 ] ) == false ) {
			$value = 10; //Single
		}

		$this->data['federal_filing_status'] = (int)$value;

		return true;
	}

	function getFederalFilingStatus() {
		if ( isset( $this->data['federal_filing_status'] ) && $this->data['federal_filing_status'] != '' ) {
			return $this->data['federal_filing_status'];
		}

		return 10; //Single
	}

	function setFederalAllowance( $value ) {
		$this->data['federal_allowance'] = $value;

		return true;
	}

	function getFederalAllowance() {
		if ( isset( $this->data['federal_allowance'] ) ) {
			return $this->data['federal_allowance'];
		}

		return false;
	}

	function setFederalFormW4Version( $value ) {
		//Check for invalid value, default to single if found.
		if ( in_array( $value, [ 2019, 2020 ] ) == false ) {
			$value = 2019; //Default to 2019 version.
		}

		$this->data['federal_form_w4_version'] = (string)$value; //2019 or 2010

		return true;
	}

	function getFederalFormW4Version() {
		if ( isset( $this->data['federal_form_w4_version'] ) ) {
			return $this->data['federal_form_w4_version'];
		}

		return 2019; //Default to 2019 version if not set.
	}


	function setFederalMultipleJobs( $value ) {
		$this->data['federal_multiple_jobs'] = (bool)$value; //Boolean Yes/No

		return true;
	}

	function getFederalMultipleJobs() {
		if ( isset( $this->data['federal_multiple_jobs'] ) ) {
			return $this->data['federal_multiple_jobs'];
		}

		return false;
	}

	function setFederalClaimDependents( $value ) {
		$this->data['federal_claim_dependents'] = $value;

		return true;
	}

	function getFederalClaimDependents() {
		if ( isset( $this->data['federal_claim_dependents'] ) ) {
			return $this->data['federal_claim_dependents'];
		}

		return false;
	}

	function setFederalOtherIncome( $value ) {
		$this->data['federal_other_income'] = $value;

		return true;
	}

	function getFederalOtherIncome() {
		if ( isset( $this->data['federal_other_income'] ) ) {
			return $this->data['federal_other_income'];
		}

		return false;
	}

	function setFederalDeductions( $value ) {
		$this->data['federal_deductions'] = $value;

		return true;
	}

	function getFederalDeductions() {
		if ( isset( $this->data['federal_deductions'] ) ) {
			return $this->data['federal_deductions'];
		}

		return false;
	}

	function setFederalAdditionalDeduction( $value ) {
		$this->data['federal_additional_deduction'] = $value;

		return true;
	}

	function getFederalAdditionalDeduction() {
		if ( isset( $this->data['federal_additional_deduction'] ) ) {
			return $this->data['federal_additional_deduction'];
		}

		return false;
	}

	function setYearToDateSocialSecurityContribution( $value ) {
		if ( $value > 0 ) {
			$this->data['social_security_ytd_contribution'] = $value;

			return true;
		}

		return false;
	}

	function getYearToDateSocialSecurityContribution() {
		if ( isset( $this->data['social_security_ytd_contribution'] ) ) {
			return $this->data['social_security_ytd_contribution'];
		}

		return 0;
	}

	function setFederalUIRate( $value ) {
		if ( $value > 0 ) {
			$retarr = $this->getDataFromRateArray( $this->getDate(), $this->federal_ui_options );
			if ( $retarr != false ) {
				if ( $value != 0 && $value < $this->getFederalUIMinimumRate() ) { //Allow a 0 rate, but nothing between 0 and the minimum.
					Debug::Text( '  Federal UI Rate is below minimum, using minimum instead: '. $value, __FILE__, __LINE__, __METHOD__, 10 );
					$value = $this->getFederalUIMinimumRate();
				}

				if ( $value > $this->getFederalUIMaximumRate() ) {
					Debug::Text( '  Federal UI Rate is above maximum, using minimum instead: '. $value, __FILE__, __LINE__, __METHOD__, 10 );
					$value = $this->getFederalUIMaximumRate();
				}
			}

			$this->data['federal_ui_rate'] = $value;

			return true;
		}

		return false;
	}

	function getFederalUIRate() {
		if ( isset( $this->data['federal_ui_rate'] ) ) {
			return $this->data['federal_ui_rate'];
		} else {
			return $this->getFederalUIMinimumRate();
		}
	}

	function setYearToDateFederalUIContribution( $value ) {
		if ( $value > 0 ) {
			$this->data['federal_ui_ytd_contribution'] = $value;

			return true;
		}

		return false;
	}

	function getYearToDateFederalUIContribution() {
		if ( isset( $this->data['federal_ui_ytd_contribution'] ) ) {
			return $this->data['federal_ui_ytd_contribution'];
		}

		return 0;
	}

	function setFederalTaxExempt( $value ) {
		$this->data['federal_tax_exempt'] = $value;

		return true;
	}

	function getFederalTaxExempt() {
		if ( isset( $this->data['federal_tax_exempt'] ) ) {
			return $this->data['federal_tax_exempt'];
		}

		return false;
	}

	//
	// State
	//


	/**
	 * Used to determine if this state tax calculation requires federal tax as an input value to be properly calculated.
	 * Mostly used outside this class to determine if we need to go through the extra work to add federal tax input values.
	 * This gets overloaded in each state class file where its TRUE.
	 * @return false
	 */
	function isFederalTaxRequired() {
		return false;
	}

	function setStateFilingStatus( $value ) {
		$value = (int)$value;
		if ( in_array( $value, [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ] ) == false ) {
			$value = 10; //Single
		}

		$this->data['state_filing_status'] = $value;

		return true;
	}

	function getStateFilingStatus() {
		if ( isset( $this->data['state_filing_status'] ) && $this->data['state_filing_status'] != '' ) {
			return $this->data['state_filing_status'];
		}

		return 10; //Single
	}

	function setStateAllowance( $value ) {
		$this->data['state_allowance'] = (int)$value; //Don't allow fractions, like 1.5 allowances, as this can cause problems with rate lookups failing when its expecting 1 or 2, and it gets 1.5

		return true;
	}

	function getStateAllowance() {
		if ( isset( $this->data['state_allowance'] ) ) {
			return $this->data['state_allowance'];
		}

		return false;
	}

	function setStateAdditionalDeduction( $value ) {
		$this->data['state_additional_deduction'] = $value;

		return true;
	}

	function getStateAdditionalDeduction() {
		if ( isset( $this->data['state_additional_deduction'] ) ) {
			return $this->data['state_additional_deduction'];
		}

		return false;
	}

	//Default to 0 unless otherwise defined in a State specific class.
	function getStateTaxPayable() {
		if ( $this->getProvincialTaxExempt() == true ) {
			Debug::text( 'State Tax Exempt!', __FILE__, __LINE__, __METHOD__, 10 );

			return 0;
		} else {
			return $this->_getStateTaxPayable();
		}
	}

	function _getStateTaxPayable() {
		return 0;
	}

	function getStatePayPeriodDeductionRoundedValue( $amount ) {
		return $amount;
	}

	function getStatePayPeriodDeductions() {
		if ( $this->getFormulaType() == 20 ) {
			Debug::text( 'Formula Type: ' . $this->getFormulaType() . ' YTD Payable: ' . $this->getStateTaxPayable() . ' YTD Paid: ' . $this->getYearToDateDeduction() . ' Current PP: ' . $this->getCurrentPayPeriod(), __FILE__, __LINE__, __METHOD__, 10 );
			$retval = $this->calcNonPeriodicDeduction( $this->getStateTaxPayable(), $this->getYearToDateDeduction() );

			//Ensure that the tax amount doesn't exceed the highest possible tax rate plus 25% for "catch-up" purposes.
			$highest_taxable_amount = bcmul( $this->getGrossPayPeriodIncome(), bcmul( $this->getStateHighestRate(), 1.25 ) );
			if ( $highest_taxable_amount > 0 && $retval > $highest_taxable_amount ) {
				$retval = $highest_taxable_amount;
				Debug::text( 'State tax amount exceeds highest tax bracket rate, capping amount at: ' . $highest_taxable_amount, __FILE__, __LINE__, __METHOD__, 10 );
			}
		} else {
			$retval = bcdiv( $this->getStateTaxPayable(), $this->getAnnualPayPeriods() );
		}

		Debug::text( 'State Pay Period Deductions: ' . $retval, __FILE__, __LINE__, __METHOD__, 10 );

		return $this->getStatePayPeriodDeductionRoundedValue( $retval );
	}

	//
	// District
	//

	//Generic district functions that handle straight percentages for any district unless otherwise overloaded.
	//for custom formulas.
	function getDistrictPayPeriodDeductions() {
		if ( $this->getFormulaType() == 20 ) {
			Debug::text( 'Formula Type: ' . $this->getFormulaType() . ' YTD Payable: ' . $this->getDistrictTaxPayable() . ' YTD Paid: ' . $this->getYearToDateDeduction() . ' Current PP: ' . $this->getCurrentPayPeriod(), __FILE__, __LINE__, __METHOD__, 10 );
			$retval = $this->calcNonPeriodicDeduction( $this->getDistrictTaxPayable(), $this->getYearToDateDeduction() );

			//Ensure that the tax amount doesn't exceed the highest possible tax rate plus 25% for "catch-up" purposes.
			$highest_taxable_amount = bcmul( $this->getGrossPayPeriodIncome(), bcmul( $this->getDistrictHighestRate(), 1.25 ) );
			if ( $highest_taxable_amount > 0 && $retval > $highest_taxable_amount ) {
				$retval = $highest_taxable_amount;
				Debug::text( 'District tax amount exceeds highest tax bracket rate, capping amount at: ' . $highest_taxable_amount, __FILE__, __LINE__, __METHOD__, 10 );
			}
		} else {
			$retval = bcdiv( $this->getDistrictTaxPayable(), $this->getAnnualPayPeriods() );
		}

		Debug::text( 'District Pay Period Deductions: ' . $retval, __FILE__, __LINE__, __METHOD__, 10 );

		return $retval;
	}

	function getDistrictAnnualTaxableIncome() {
		$annual_income = $this->getAnnualTaxableIncome();

		return $annual_income;
	}

	function getDistrictTaxPayable() {
		$annual_income = $this->getDistrictAnnualTaxableIncome();

		if ( $annual_income > 0 ) {
			$rate = bcdiv( $this->getUserValue2(), 100 );
			$retval = bcmul( $annual_income, $rate );
		}

		if ( !isset( $retval ) || $retval < 0 ) {
			$retval = 0;
		}

		Debug::text( 'zzDistrict Annual Tax Payable: ' . $retval . ' User Value 2: ' . $this->getUserValue2() . ' Annual Income: ' . $annual_income, __FILE__, __LINE__, __METHOD__, 10 );

		return $retval;
	}

	function setDistrictFilingStatus( $value ) {
		$this->data['district_filing_status'] = $value;

		return true;
	}

	function getDistrictFilingStatus() {
		if ( isset( $this->data['district_filing_status'] ) ) {
			return $this->data['district_filing_status'];
		}

		return 10; //Single
	}

	function setDistrictAllowance( $value ) {
		$this->data['district_allowance'] = $value;

		return true;
	}

	function getDistrictAllowance() {
		if ( isset( $this->data['district_allowance'] ) ) {
			return $this->data['district_allowance'];
		}

		return false;
	}

	function setYearToDateStateUIContribution( $value ) {
		if ( $value > 0 ) {
			$this->data['state_ui_ytd_contribution'] = $value;

			return true;
		}

		return false;
	}

	function getYearToDateStateUIContribution() {
		if ( isset( $this->data['state_ui_ytd_contribution'] ) ) {
			return $this->data['state_ui_ytd_contribution'];
		}

		return 0;
	}

	function setStateUIRate( $value ) {
		if ( $value > 0 ) {
			$this->data['state_ui_rate'] = $value;

			return true;
		}

		return false;
	}

	function getStateUIRate() {
		if ( isset( $this->data['state_ui_rate'] ) ) {
			return $this->data['state_ui_rate'];
		}

		return 0;
	}

	function setStateUIWageBase( $value ) {
		if ( $value > 0 ) {
			$this->data['state_ui_wage_base'] = $value;

			return true;
		}

		return false;
	}

	function getStateUIWageBase() {
		if ( isset( $this->data['state_ui_wage_base'] ) ) {
			return $this->data['state_ui_wage_base'];
		} else {
			return $this->getStateUIDefaultWageBase();
		}

		return 0;
	}

	function getStateUIDefaultWageBase() {
		$retarr = $this->getDataFromRateArray( $this->getDate(), $this->state_ui_options );
		if ( $retarr == false ) {
			return false;
		}

		if ( isset( $retarr['wage_base'] ) && !empty( $retarr['wage_base'] ) ) {
			return $retarr['wage_base'];
		}

		return 0;
	}

	function setProvincialTaxExempt( $value ) {
		$this->data['provincial_tax_exempt'] = $value;

		return true;
	}

	function getProvincialTaxExempt() {
		if ( isset( $this->data['provincial_tax_exempt'] ) ) {
			return $this->data['provincial_tax_exempt'];
		}

		return false;
	}

	function setSocialSecurityExempt( $value ) {
		$this->data['social_security_exempt'] = $value;

		return true;
	}

	function getSocialSecurityExempt() {
		if ( isset( $this->data['social_security_exempt'] ) ) {
			return $this->data['social_security_exempt'];
		}

		return false;
	}

	function setMedicareExempt( $value ) {
		$this->data['medicare_exempt'] = $value;

		return true;
	}

	function getMedicareExempt() {
		if ( isset( $this->data['medicare_exempt'] ) ) {
			return $this->data['medicare_exempt'];
		}

		return false;
	}

	function setUIExempt( $value ) {
		$this->data['ui_exempt'] = $value;

		return true;
	}

	function getUIExempt() {
		if ( isset( $this->data['ui_exempt'] ) ) {
			return $this->data['ui_exempt'];
		}

		return false;
	}

	//
	// Calculation Functions
	//
	function getAnnualTaxableIncome() {
		if ( $this->getFormulaType() == 20 ) {
			Debug::text( 'Formula Type: ' . $this->getFormulaType() . ' YTD Gross: ' . $this->getYearToDateGrossIncome() . ' This Gross: ' . $this->getGrossPayPeriodIncome() . ' Current PP: ' . $this->getCurrentPayPeriod(), __FILE__, __LINE__, __METHOD__, 10 );
			$retval = $this->calcNonPeriodicIncome( $this->getYearToDateGrossIncome(), $this->getGrossPayPeriodIncome() );
		} else {
			$retval = bcmul( $this->getGrossPayPeriodIncome(), $this->getAnnualPayPeriods() );
		}
		Debug::text( 'Annual Taxable Income: ' . $retval, __FILE__, __LINE__, __METHOD__, 10 );

		if ( $retval < 0 ) {
			$retval = 0;
		}

		return $retval;
	}

	//
	// Federal Tax
	//
	function getFederalPayPeriodDeductions() {
		if ( $this->getFormulaType() == 20 ) {
			Debug::text( 'Formula Type: ' . $this->getFormulaType() . ' YTD Payable: ' . $this->getFederalTaxPayable() . ' YTD Paid: ' . $this->getYearToDateDeduction() . ' Current PP: ' . $this->getCurrentPayPeriod(), __FILE__, __LINE__, __METHOD__, 10 );
			$retval = $this->calcNonPeriodicDeduction( $this->getFederalTaxPayable(), $this->getYearToDateDeduction() );

			//Ensure that the tax amount doesn't exceed the highest possible tax rate plus 25% for "catch-up" purposes.
			$highest_taxable_amount = bcmul( $this->getGrossPayPeriodIncome(), bcmul( $this->getFederalHighestRate(), 1.25 ) );
			if ( $highest_taxable_amount > 0 && $retval > $highest_taxable_amount ) {
				$retval = $highest_taxable_amount;
				Debug::text( 'Federal tax amount exceeds highest tax bracket rate, capping amount at: ' . $highest_taxable_amount, __FILE__, __LINE__, __METHOD__, 10 );
			}
		} else {
			$retval = bcdiv( $this->getFederalTaxPayable(), $this->getAnnualPayPeriods() );
		}

		Debug::text( 'Federal Pay Period Deductions: ' . $retval, __FILE__, __LINE__, __METHOD__, 10 );

		return $retval;
	}

	function getFederalTaxPayable() {
		if ( $this->getFederalTaxExempt() == true ) {
			Debug::text( 'Federal Tax Exempt!', __FILE__, __LINE__, __METHOD__, 10 );

			return 0;
		}

		$annual_taxable_income = $this->getAnnualTaxableIncome();
		if ( $this->getDate() >= 20200101 && $this->getFederalFormW4Version() == 2020 ) { //See Form W4 Version check below as well.
			$annual_taxable_income = bcadd( $annual_taxable_income, $this->getFederalOtherIncome() );

			$filing_status_adjustment = 0;
			if ( $this->getFederalMultipleJobs() == false ) {
				if ( $this->getFederalFilingStatus() == 20 ) {                                                    //Married Filing Jointly
					$filing_status_adjustment = bcmul( $this->getFederalAllowanceAmount( $this->getDate() ), 3 ); //$12,600 (4,200 * 3 )
				} else {
					$filing_status_adjustment = bcmul( $this->getFederalAllowanceAmount( $this->getDate() ), 2 ); //$8,400 ( 4,200 * 2 )
				}
			}
			Debug::text( 'Filing Status Adjustment: ' . $filing_status_adjustment . ' W4 Deductions: ' . $this->getFederalDeductions(), __FILE__, __LINE__, __METHOD__, 10 );

			$annual_taxable_income = bcsub( bcsub( $annual_taxable_income, $filing_status_adjustment ), $this->getFederalDeductions() );
			Debug::text( '2020 W4 - Annual Taxable Income: ' . $annual_taxable_income . ' Other Income: ' . $this->getFederalOtherIncome() . ' ', __FILE__, __LINE__, __METHOD__, 10 );
		} else {
			$annual_allowance = bcmul( $this->getFederalAllowanceAmount( $this->getDate() ), $this->getFederalAllowance() );
			Debug::text( 'Legacy W4 - Annual Taxable Income: ' . $annual_taxable_income . 'Allowance: ' . $annual_allowance, __FILE__, __LINE__, __METHOD__, 10 );

			if ( $annual_taxable_income > $annual_allowance ) {
				$annual_taxable_income = bcsub( $annual_taxable_income, $annual_allowance );
			} else {
				Debug::text( 'Income is less then allowance: ', __FILE__, __LINE__, __METHOD__, 10 );
				$annual_taxable_income = 0;
			}
		}

		if ( $annual_taxable_income > 0 ) {
			Debug::text( 'Annual Taxable Income: ' . $annual_taxable_income, __FILE__, __LINE__, __METHOD__, 10 );
			$rate = $this->getData()->getFederalRate( $annual_taxable_income );
			$federal_constant = $this->getData()->getFederalConstant( $annual_taxable_income );
			$federal_rate_income = $this->getData()->getFederalRatePreviousIncome( $annual_taxable_income );

			$retval = bcadd( bcmul( bcsub( $annual_taxable_income, $federal_rate_income ), $rate ), $federal_constant );

			if ( $this->getDate() >= 20200101 && $this->getFederalFormW4Version() == 2020 ) {  //See Form W4 Version check above as well.
				Debug::text( '  Claimed Dependent Amount: ' . $this->getFederalClaimDependents(), __FILE__, __LINE__, __METHOD__, 10 );
				$retval = bcsub( $retval, $this->getFederalClaimDependents() );
			}

			if ( $retval < 0 ) {
				$retval = 0;
			}
		} else {
			$retval = 0;
		}

		//Additional deduction must be added at the very end, even if annual income is less than 0.
		if ( $this->getDate() >= 20200101 && $this->getFederalFormW4Version() == 2020 ) {  //See Form W4 Version check above as well.
			if ( $annual_taxable_income <= 0 ) { //If annual taxable income is 0, we don't bother getting tax rates above, so we need to get them now so getFederalHighestRate() works below.
				$this->getData();
			}

			$additional_deduction = 0;

			//Don't deduct the additional withholding during out-of-cycle payroll runs, or when using the non-periodic calculations as the non-periodic formula contradicts the purpose of additional withholding.
			// Especially if they change the additional withholding it will try to calculate what was (or was not) owed retroactively to the beginning of the year too.
			// Both additional withholding and regular withholding is combined into a single pay stub account so we can't separate if we did wanted to handle them differently somehow anyways.
			// The only way to do always non-periodic tax calculation and additional withhold would be with a separate pay stub account.
			// Also cap additional withholding at the highest tax rate plus a buffer, to ensure it never exceeds the employees gross earnings.
			if ( $this->getFederalAdditionalDeduction() > 0 && $this->getFormulaType() == 10 ) {
				$maximum_tax_rate_threshold = bcmul( $this->getFederalHighestRate(), 1.50 );
				if ( $maximum_tax_rate_threshold > 0.80 ) { //Hopefully unlikely, but some states have odd tax brackets with high rates for small dollar amount brackets. So make sure no tax rate happens to exceed 80%.
					$maximum_tax_rate_threshold = 0.80;
				}

				$maximum_additional_deduction = bcmul( $this->getGrossPayPeriodIncome(), $maximum_tax_rate_threshold );
				if ( $this->getFederalAdditionalDeduction() > $maximum_additional_deduction ) {
					$additional_deduction = $maximum_additional_deduction;
					Debug::text( '  Additional Deduction exceeds maximum threshold of highest rate plus buffer, capping: ' . $maximum_additional_deduction, __FILE__, __LINE__, __METHOD__, 10 );
				} else {
					$additional_deduction = $this->getFederalAdditionalDeduction();
				}

				$additional_deduction = bcmul( $additional_deduction, $this->getAnnualPayPeriods() ); //Federal Deduction amount from 2020 W4 is *PER PAY PERIOD*
			}

			Debug::text( '  Additional Deduction: ' . $additional_deduction, __FILE__, __LINE__, __METHOD__, 10 );
			$retval = bcadd( $retval, $additional_deduction );
		}

		Debug::text( 'RetVal: ' . $retval, __FILE__, __LINE__, __METHOD__, 10 );
		return $retval;
	}

	//
	// Social Security
	//
	function getAnnualEmployeeSocialSecurity() {
		if ( $this->getSocialSecurityExempt() == true ) {
			return 0;
		}

		$annual_income = $this->getAnnualTaxableIncome();
		$rate = bcdiv( $this->getSocialSecurityRate(), 100 );
		$maximum_contribution = $this->getSocialSecurityMaximumContribution();

		Debug::text( 'Rate: ' . $rate . ' Maximum Contribution: ' . $maximum_contribution, __FILE__, __LINE__, __METHOD__, 10 );

		$amount = bcmul( $annual_income, $rate );
		$max_amount = $maximum_contribution;

		if ( $amount > $max_amount ) {
			$retval = $max_amount;
		} else {
			$retval = $amount;
		}

		if ( $retval < 0 ) {
			$retval = 0;
		}

		return $retval;
	}

	function getEmployeeSocialSecurity() {
		if ( $this->getSocialSecurityExempt() == true ) {
			return 0;
		}

		$type = 'employee';

		$pay_period_income = $this->getGrossPayPeriodIncome();
		$rate = bcdiv( $this->getSocialSecurityRate( $type ), 100 );
		$maximum_contribution = $this->getSocialSecurityMaximumContribution( $type );
		$ytd_contribution = $this->getYearToDateSocialSecurityContribution();

		Debug::text( 'Rate: ' . $rate . ' YTD Contribution: ' . $ytd_contribution .' Max Contribution: '. $maximum_contribution, __FILE__, __LINE__, __METHOD__, 10 );

		$amount = bcmul( $pay_period_income, $rate );
		$max_amount = bcsub( $maximum_contribution, $ytd_contribution );

		if ( $amount > $max_amount ) {
			$retval = $max_amount;
		} else {
			$retval = $amount;
		}

		if ( $retval < 0 ) {
			$retval = 0;
		}

		return $retval;
	}

	function getEmployerSocialSecurity() {
		if ( $this->getSocialSecurityExempt() == true ) {
			return 0;
		}

		$type = 'employer';

		$pay_period_income = $this->getGrossPayPeriodIncome();
		$rate = bcdiv( $this->getSocialSecurityRate( $type ), 100 );
		$maximum_contribution = $this->getSocialSecurityMaximumContribution( $type );
		$ytd_contribution = $this->getYearToDateSocialSecurityContribution();

		Debug::text( 'Rate: ' . $rate . ' YTD Contribution: ' . $ytd_contribution .' Max Contribution: '. $maximum_contribution, __FILE__, __LINE__, __METHOD__, 10 );

		$amount = bcmul( $pay_period_income, $rate );
		$max_amount = bcsub( $maximum_contribution, $ytd_contribution );

		if ( $amount > $max_amount ) {
			$retval = $max_amount;
		} else {
			$retval = $amount;
		}

		if ( $retval < 0 ) {
			$retval = 0;
		}

		return $retval;
	}


	//
	// Medicare
	//
	function getAnnualEmployeeMedicare() {
		return bcmul( $this->getEmployeeMedicare(), $this->getAnnualPayPeriods() );
	}

	function getEmployeeMedicare() {
		if ( $this->getMedicareExempt() == true ) {
			return 0;
		}

		$pay_period_income = $this->getGrossPayPeriodIncome();

		$rate_data = $this->getMedicareRate();
		$rate = bcdiv( $rate_data['employee_rate'], 100 );
		Debug::text( 'Rate: ' . $rate, __FILE__, __LINE__, __METHOD__, 10 );

		$amount = round( bcmul( $pay_period_income, $rate ), 2 ); //Must round separately from additional medicare, as they are broken out in tax reports.
		Debug::text( 'Amount: ' . $amount, __FILE__, __LINE__, __METHOD__, 10 );

		$threshold_income = $this->getMedicareAdditionalEmployerThreshold();
		Debug::text( 'Threshold Income: ' . $threshold_income, __FILE__, __LINE__, __METHOD__, 10 );
		if ( $threshold_income > 0 && bcadd( $this->getYearToDateGrossIncome(), $this->getGrossPayPeriodIncome() ) > $threshold_income ) {
			if ( $this->getYearToDateGrossIncome() < $threshold_income ) {
				$threshold_income = bcsub( bcadd( $this->getYearToDateGrossIncome(), $this->getGrossPayPeriodIncome() ), $threshold_income );
			} else {
				$threshold_income = $pay_period_income;
			}
			Debug::text( 'bThreshold Income: ' . $threshold_income, __FILE__, __LINE__, __METHOD__, 10 );
			$threshold_amount = round( bcmul( $threshold_income, bcdiv( $rate_data['employee_threshold_rate'], 100 ) ), 2 ); //Must round separately from regular medicare, as they are broken out in tax reports.
			Debug::text( 'Threshold Amount: ' . $threshold_amount, __FILE__, __LINE__, __METHOD__, 10 );
			$amount = bcadd( $amount, $threshold_amount );
		}

		if ( $amount < 0 ) {
			$amount = 0;
		}

		return $amount;
	}

	function getEmployerMedicare() {
		//return $this->getEmployeeMedicare();
		if ( $this->getMedicareExempt() == true ) {
			return 0;
		}

		$pay_period_income = $this->getGrossPayPeriodIncome();

		$rate_data = $this->getMedicareRate();
		$rate = bcdiv( $rate_data['employer_rate'], 100 );
		Debug::text( 'Rate: ' . $rate, __FILE__, __LINE__, __METHOD__, 10 );

		$amount = bcmul( $pay_period_income, $rate );

		if ( $amount < 0 ) {
			$amount = 0;
		}

		return $amount;
	}

	//
	// Federal UI
	//
	function getFederalEmployerUI() {
		if ( $this->getUIExempt() == true ) {
			return 0;
		}

		$pay_period_income = $this->getGrossPayPeriodIncome();
		$rate = bcdiv( $this->getFederalUIRate(), 100 );
		$maximum_contribution = $this->getFederalUIMaximumContribution();
		$ytd_contribution = $this->getYearToDateFederalUIContribution();

		Debug::text( 'Rate: ' . $rate . ' YTD Contribution: ' . $ytd_contribution . ' Maximum: ' . $maximum_contribution, __FILE__, __LINE__, __METHOD__, 10 );

		$amount = bcmul( $pay_period_income, $rate );
		$max_amount = bcsub( $maximum_contribution, $ytd_contribution );

		if ( $amount > $max_amount ) {
			$retval = $max_amount;
		} else {
			$retval = $amount;
		}

		if ( $retval < 0 ) {
			$retval = 0;
		}

		return $retval;
	}

	//
	// State UI
	//
	function getStateEmployerUI() {
		if ( $this->getUIExempt() == true ) {
			return 0;
		}

		$pay_period_income = $this->getGrossPayPeriodIncome();
		$rate = bcdiv( $this->getStateUIRate(), 100 );
		$maximum_contribution = bcmul( $this->getStateUIWageBase(), $rate );
		$ytd_contribution = $this->getYearToDateStateUIContribution();

		Debug::text( 'Rate: ' . $rate . ' YTD Contribution: ' . $ytd_contribution . ' Maximum: ' . $maximum_contribution, __FILE__, __LINE__, __METHOD__, 10 );

		$amount = bcmul( $pay_period_income, $rate );
		$max_amount = bcsub( $maximum_contribution, $ytd_contribution );

		if ( $amount > $max_amount ) {
			$retval = $max_amount;
		} else {
			$retval = $amount;
		}

		if ( $retval < 0 ) {
			$retval = 0;
		}

		return $retval;
	}

	function getPayPeriodTaxDeductions() {
		return bcadd( $this->getFederalPayPeriodDeductions(), $this->getStatePayPeriodDeductions() );
	}

	function getPayPeriodEmployeeTotalDeductions() {
		return bcadd( bcadd( $this->getPayPeriodTaxDeductions(), $this->getEmployeeSocialSecurity() ), $this->getEmployeeMedicare() );
	}

	function getPayPeriodEmployeeNetPay() {
		return bcsub( $this->getGrossPayPeriodIncome(), $this->getPayPeriodEmployeeTotalDeductions() );
	}

	function RoundNearestDollar( $amount ) {
		return round( $amount, 0 );
	}

	/*
		Use this to get all useful values.
	*/
	function getArray() {

		$array = [
				'gross_pay'                => $this->getGrossPayPeriodIncome(),
				'federal_tax'              => $this->getFederalPayPeriodDeductions(),
				'state_tax'                => $this->getStatePayPeriodDeductions(),
				/*
										'employee_social_security' => $this->getEmployeeSocialSecurity(),
										'employer_social_security' => $this->getEmployeeSocialSecurity(),
										'employee_medicare' => $this->getEmployeeMedicare(),
										'employer_medicare' => $this->getEmployerMedicare(),
				*/
				'employee_social_security' => $this->getEmployeeSocialSecurity(),
				'federal_employer_ui'      => $this->getFederalEmployerUI(),
				//						'state_employer_ui' => $this->getStateEmployerUI(),

		];

		Debug::Arr( $array, 'Deductions Array:', __FILE__, __LINE__, __METHOD__, 10 );

		return $array;
	}
}

?>