package jp.mosp.payroll.base;

import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import jp.mosp.common.common.MospException;
import jp.mosp.common.common.MospUtility;
import jp.mosp.payroll.dto.DetailDto;
import jp.mosp.payroll.dto.DetailMoneyDto;
import jp.mosp.payroll.dto.DetailPbDto;
import jp.mosp.payroll.dto.DetailTimeDto;
import jp.mosp.payroll.dto.PhPaymentDto;
import jp.mosp.payroll.dto.DetailPaDto;
import jp.mosp.payroll.dto.DetailPdDto;

/**
 * 給与明細クラス。<br>
 */
public class Payment extends PayrollSlip {
	
	private static final long serialVersionUID = -6106880371454177891L;
	
	/**
	 * 給与ヘッダー情報。<br>
	 */
	private PhPaymentDto header;
	
	/**
	 * 勤怠明細情報。<br>
	 */
	private List<DetailTimeDto> time;
	
	/**
	 * 支給明細情報。<br>
	 */
	private List<DetailPaDto> allowance;
	
	/**
	 * 控除明細情報。<br>
	 */
	private List<DetailPdDto> deduction;
	
	/**
	 * 内訳明細情報。<br>
	 */
	private List<DetailPbDto> breakdown;
	
	/**
	 * 合計明細情報。<br>
	 */
	private List<DetailMoneyDto> sum;
	
	/**
	 * インスタンスを生成し、フィールドを設定する。<br>
	 * @param header    給与ヘッダー情報
	 * @param time      勤怠明細情報
	 * @param allowance 支給明細情報
	 * @param deduction 控除明細情報
	 * @param breakdown 内訳明細情報
	 * @param sum       合計明細情報
	 */
	public Payment(
			PhPaymentDto         header   ,
			List<DetailTimeDto > time     ,
			List<DetailPaDto   > allowance,
			List<DetailPdDto   > deduction,
			List<DetailPbDto   > breakdown,
			List<DetailMoneyDto> sum      
	) {
		this.header    = header   ;
		this.time      = time     ;
		this.allowance = allowance;
		this.deduction = deduction;
		this.breakdown = breakdown;
		this.sum       = sum      ;
		initDetails();
	}
	
	/**
	 * インスタンスを生成し、フィールドを設定する。<br>
	 * @param header          給与ヘッダー情報
	 * @param time            勤怠明細情報
	 * @param allowance       支給明細情報
	 * @param deduction       控除明細情報
	 * @param breakdown       内訳明細情報
	 * @param sum             合計明細情報
	 * @param amountTime      勤怠明細情報
	 * @param amountAllowance 支給明細情報
	 * @param amountDeduction 控除明細情報
	 * @param amountBreakdown 内訳明細情報
	 * @param amountSum       合計明細情報
	 */
	public Payment(
			PhPaymentDto         header         ,
			List<DetailTimeDto > time           ,
			List<DetailPaDto   > allowance      ,
			List<DetailPdDto   > deduction      ,
			List<DetailPbDto   > breakdown,
			List<DetailMoneyDto> sum            ,
			List<DetailTimeDto > amountTime     ,
			List<DetailMoneyDto> amountAllowance,
			List<DetailMoneyDto> amountDeduction,
			List<DetailTimeDto > amountBreakdown,
			List<DetailMoneyDto> amountSum      
	) {
		this.header    = header   ;
		this.time      = time     ;
		this.allowance = allowance;
		this.deduction = deduction;
		this.breakdown = breakdown;
		this.sum       = sum      ;
		initDetails();
		setDetailsAmount(time     , amountTime     );
		setDetailsAmount(allowance, amountAllowance);
		setDetailsAmount(deduction, amountDeduction);
		setDetailsAmount(breakdown, amountBreakdown);
		setDetailsAmount(sum      , amountSum      );
	}
	
	/**
	 * 明細を初期化する。
	 */
	private void initDetails() {
		initDetails(time     , getKCode(), getCalcMonth());
		initDetails(allowance, getKCode(), getCalcMonth());
		initDetails(deduction, getKCode(), getCalcMonth());
		initDetails(breakdown, getKCode(), getCalcMonth());
		initDetails(sum      , getKCode(), getCalcMonth());
	}
	
	/**
	 * 明細値を計算する。
	 * @param mapParams パラメータMap
	 * @throws MospException 
	 */
	public void calcDetails(Map<String, String> mapParams) throws MospException {
		calcDetails(time     , mapParams);
		calcDetails(allowance, mapParams);
		calcDetails(deduction, mapParams);
		calcDetails(breakdown, mapParams);
		calcDetails(sum      , mapParams);
	}
	
	/**
	 * 明細値を設定する。
	 * @param aryTime      勤怠明細値
	 * @param aryAllowance 支給明細値
	 * @param aryDeduction 控除明細値
	 * @param aryBreakdown 内訳明細値
	 */
	public void setDetails(
			String[][] aryTime     ,
			String[][] aryAllowance,
			String[][] aryDeduction,
			String[][] aryBreakdown
	) {
		setDetails(time     , aryTime     );
		setDetails(allowance, aryAllowance);
		setDetails(deduction, aryDeduction);
		setDetails(breakdown, aryBreakdown);
	}
	
	/**
	 * 勤怠明細値を設定する。
	 * @param detailCode 明細コード
	 * @param amount 明細値
	 */
	public void setTimeAmount(String detailCode, float amount) {
		setDetailAmount(getDetailDto(time, detailCode), amount);
	}
	
	/**
	 * 支給明細値を設定する。
	 * @param detailCode 明細コード
	 * @param amount 明細値
	 */
	public void setAllowanceAmount(String detailCode, int amount) {
		setDetailAmount(getDetailDto(allowance, detailCode), amount);
	}
	
	/**
	 * 控除明細値を設定する。
	 * @param detailCode 明細コード
	 * @param amount 明細値
	 */
	public void setDeductionAmount(String detailCode, int amount) {
		setDetailAmount(getDetailDto(deduction, detailCode), amount);
	}
	
	/**
	 * 内訳明細値を設定する。
	 * @param detailCode 明細コード
	 * @param amount 明細値
	 */
	public void setBreakdownAmount(String detailCode, int amount) {
		setDetailAmount(getDetailDto(breakdown, detailCode), amount);
	}
	
	/**
	 * 合計明細値を設定する。
	 * @param detailCode 明細コード
	 * @param amount 明細値
	 */
	public void setSumAmount(String detailCode, int amount) {
		setDetailAmount(getDetailDto(sum, detailCode), amount);
	}
	
	/**
	 * 勤怠明細値を取得する。
	 * @param detailCode 明細コード
	 * @return 明細値
	 */
	public float getTimeAmount(String detailCode) {
		return getDetailAmount(time, detailCode);
	}
	
	/**
	 * 支給明細値を取得する。
	 * @param detailCode 明細コード
	 * @return 明細値
	 */
	public int getAllowanceAmount(String detailCode) {
		return (int)getDetailAmount(allowance, detailCode);
	}
	
	/**
	 * 控除明細値を取得する。
	 * @param detailCode 明細コード
	 * @return 明細値
	 */
	public int getDeductionAmount(String detailCode) {
		return (int)getDetailAmount(deduction, detailCode);
	}
	
	/**
	 * 内訳明細値を取得する。
	 * @param detailCode 明細コード
	 * @return 明細値
	 */
	public float getBreakdownAmount(String detailCode) {
		return getDetailAmount(breakdown, detailCode);
	}
	
	/**
	 * 合計明細値を取得する。
	 * @param detailCode 明細コード
	 * @return 明細値
	 */
	public int getSumAmount(String detailCode) {
		return (int)getDetailAmount(sum, detailCode);
	}
	
	/**
	 * 社員コードを設定する。
	 * @param kCode 設定する社員コード
	 */
	public void setKCode(String kCode) {
		header.setKCode(kCode);
	}
	
	/**
	 * 社員コードを取得する。<br>
	 * @return 社員コード
	 */
	public String getKCode() {
		return header.getKCode();
	}
	
	/**
	 * 計算年月日を設定する。<br>
	 * @param calcDate 設定する計算年月日
	 */
	public void setCalcDate(Date calcDate) {
		header.setCalcMonth(calcDate);
	}
	
	/**
	 * 計算年月を取得する。<br>
	 * @return 計算年月
	 */
	public Date getCalcMonth() {
		return header.getCalcMonth();
	}
	
	/**
	 * 支給日を設定する。<br>
	 * @param payDate 設定する支給日
	 */
	public void setPayDate(Date payDate) {
		header.setPayDate(payDate);
	}
	
	/**
	 * 伝票コードを設定する。<br>
	 * @param slipCode 設定する伝票コード
	 */
	public void setSlipCode(String slipCode) {
		header.setSlipCode(slipCode);
	}
	
	/**
	 * 給与ヘッダーを取得する。
	 * @return 明細値
	 */
	public PhPaymentDto getHeader() {
		return header;
	}
	
	/**
	 * 勤怠明細配列を取得する。
	 * @param checkDisplayType 0印字区分確認要否
	 * @return 明細配列
	 */
	public String[][] getTimeArray(boolean checkDisplayType) {
		return getAryDetail(time, checkDisplayType);
	}
	
	/**
	 * 支給明細配列を取得する。
	 * @param checkDisplayType 0印字区分確認要否
	 * @return 明細配列
	 */
	public String[][] getAllowanceArray(boolean checkDisplayType) {
		return getAryDetail(allowance, checkDisplayType);
	}
	
	/**
	 * 控除明細配列を取得する。
	 * @param checkDisplayType 0印字区分確認要否
	 * @return 明細配列
	 */
	public String[][] getDeductionArray(boolean checkDisplayType) {
		return getAryDetail(deduction, checkDisplayType);
	}
	
	/**
	 * 内訳明細配列を取得する。
	 * @param checkDisplayType 0印字区分確認要否
	 * @return 明細配列
	 */
	public String[][] getBreakdownArray(boolean checkDisplayType) {
		List<DetailPbDto> view = new ArrayList<DetailPbDto>();
		for (Iterator<DetailPbDto> it =breakdown.iterator(); it.hasNext();) {
			DetailPbDto dto = it.next();
			if (dto.getViewType() == PayrollConst.DETAIL_VIEW_ON) {
				view.add(dto);
			}
		}
		return getAryDetail(view, checkDisplayType);
	}
	
	/**
	 * 合計明細配列を取得する。
	 * @param checkDisplayType 0印字区分確認要否
	 * @return 明細配列
	 */
	public String[][] getSumArray(boolean checkDisplayType) {
		return getAryDetail(sum, checkDisplayType);
	}
	
	/**
	 * 勤怠明細リストを取得する。
	 * @return 明細リスト
	 */
	public List<DetailTimeDto> getTimeList() {
		return time;
	}
	
	/**
	 * 支給明細リストを取得する。
	 * @return 明細リスト
	 */
	public List<DetailPaDto> getAllowanceList() {
		return allowance;
	}
	
	/**
	 * 控除明細リストを取得する。
	 * @return 明細リスト
	 */
	public List<DetailPdDto> getDeductionList() {
		return deduction;
	}
	
	/**
	 * 内訳明細リストを取得する。
	 * @return 明細リスト
	 */
	public List<DetailPbDto> getBreakdownList() {
		return breakdown;
	}
	
	/**
	 * 合計明細リストを取得する。
	 * @return 明細リスト
	 */
	public List<DetailMoneyDto> getSumList() {
		return sum;
	}
	
	/**
	 * 計算年月文字列を取得する。<br>
	 * @return 計算年月文字列
	 */
	public String getCalcMonthStr() {
		return MospUtility.getDateString(getCalcMonth(), PayrollConst.FORMAT_YEAR_MONTH);
	}
	
	/**
	 * 支給日を取得する。
	 * @return 支給日
	 */
	public String getPayDate() {
		return MospUtility.getDateString(header.getPayDate(), PayrollConst.FORMAT_DATE);
	}
	
	/**
	 * 課税対象額を取得する。
	 * @return 課税対象額
	 */
	public int getTaxation() {
		return header.getTaxation();
	}

	/**
	 * 雇用保険対象額を取得する。
	 * @return 雇用保険対象額
	 */
	public int getInsureUnemploy() {
		return header.getInsureUnemploy();
	}

	/**
	 * 社会保険合計を取得する。
	 * @return 社会保険合計
	 */
	public int getSocInsTotal() {
		return header.getSocInsTotal();
	}

	/**
	 * 扶養等の数を取得する。
	 * @return 扶養等の数
	 */
	public int getDependent() {
		return header.getDependent();
	}
	
	/**
	 * 所得税額を取得する。
	 * @return 所得税額
	 */
	public int getIncomeTax() {
		return header.getIncomeTax();
	}
	
	/**
	 * 賞与計算対象額を取得する。
	 * @return 賞与計算対象額
	 */
	public int getBonusBase() {
		return header.getBonusBase();
	}
	
	/**
	 * 総支給金額を取得する。
	 * @return 総支給金額
	 */
	public int getAllowanceTotal() {
		return header.getAllowanceTotal();
	}
	
	/**
	 * 総控除金額を取得する。
	 * @return 総控除金額
	 */
	public int getDeductionTotal() {
		return header.getDeductionTotal();
	}
	
	/**
	 * 差引支給金額を取得する。
	 * @return 差引支給金額
	 */
	public int getPaymentTotal() {
		return header.getPaymentTotal();
	}
	
	/**
	 * 通勤手当を設定する。
	 * @param amount 通勤手当
	 */
	public void setCommutation(int amount) {
		setDetailAmount(getDetailDto(allowance, PayrollConst.DTL_COMMUTATION), amount);
	}
	
	/**
	 * 課税通勤手当を設定する。
	 * @param amount 課税通勤手当
	 */
	public void setCommuteTax(int amount) {
		setDetailAmount(getDetailDto(breakdown, PayrollConst.DTL_COMMUTE_TAX), amount);
	}
	
	/**
	 * 健康保険料を設定する。
	 * @param amount 健康保険料
	 */
	public void setHealthInsurance(int amount) {
		setDetailAmount(getDetailDto(deduction, PayrollConst.DTL_HEALTH_INS), amount);
	}
	
	/**
	 * 介護保険料を設定する。
	 * @param amount 介護保険料
	 */
	public void setNursingInsurance(int amount) {
		setDetailAmount(getDetailDto(deduction, PayrollConst.DTL_NURSING_INS), amount);
	}
	
	/**
	 * 厚生年金料を設定する。
	 * @param amount 厚生年金料
	 */
	public void setPensionInsurance(int amount) {
		setDetailAmount(getDetailDto(deduction, PayrollConst.DTL_PENSION_INS), amount);
	}
	
	/**
	 * 厚生年金基金料を設定する。
	 * @param amount 厚生年金基金料
	 */
	public void setPensionFund(int amount) {
		setDetailAmount(getDetailDto(deduction, PayrollConst.DTL_PENSION_FUND), amount);
	}
	
	/**
	 * 雇用保険料を設定する。
	 * @param amount 雇用保険料
	 */
	public void setEmployeeInsurance(int amount) {
		setDetailAmount(getDetailDto(deduction, PayrollConst.DTL_EMPLOYEE_INS), amount);
	}
	
	/**
	 * 所得税を設定する。
	 * @param amount 設定する所得税
	 */
	public void setIncomeTax(int amount) {
		setDetailAmount(getDetailDto(deduction, PayrollConst.DTL_INCOME_TAX), amount);
		header.setIncomeTax(amount);
	}
	
	/**
	 * 住民税を設定する。
	 * @param amount 設定する住民税
	 */
	public void setResidenceTax(int amount) {
		setDetailAmount(getDetailDto(deduction, PayrollConst.DTL_RESIDENCE_TAX), amount);
	}
	
	/**
	 * 銀行1振込額を設定する。
	 * @param amount 設定する銀行1振込額
	 */
	public void setBank1PayAmount(int amount) {
		setDetailAmount(getDetailDto(sum, PayrollConst.DTL_SUM_BANK1), amount);
	}
	
	/**
	 * 銀行2振込額を設定する。
	 * @param amount 設定する銀行2振込額
	 */
	public void setBank2PayAmount(int amount) {
		setDetailAmount(getDetailDto(sum, PayrollConst.DTL_SUM_BANK2), amount);
	}
	
	/**
	 * 現金支給額を設定する。
	 * @param amount 設定する銀行1振込額
	 */
	public void setCashPayAmount(int amount) {
		setDetailAmount(getDetailDto(sum, PayrollConst.DTL_SUM_CASH), amount);
	}
	
	/**
	 * 社会保険合計を計算する。
	 */
	public void calcSocInsTotal() {
		long sum = 
			getDeductionAmount(PayrollConst.DTL_HEALTH_INS  ) +
			getDeductionAmount(PayrollConst.DTL_NURSING_INS ) +
			getDeductionAmount(PayrollConst.DTL_PENSION_INS ) +
			getDeductionAmount(PayrollConst.DTL_PENSION_FUND) +
			getDeductionAmount(PayrollConst.DTL_EMPLOYEE_INS);
		setSumAmount(PayrollConst.DTL_SUM_SOC_INS, (int)sum);
		header.setSocInsTotal((int)sum);
	}
	
	/**
	 * 総支給金額を計算する。
	 */
	public void calcAllowance() {
		long sum = 0L;
		for (Iterator<DetailPaDto> it = allowance.iterator(); it.hasNext();) {
			sum += it.next().getAmount();
		}
		sum = sum < 0 ? 0 : sum;
		setSumAmount(PayrollConst.DTL_SUM_ALLOWANCE, (int)sum);
		header.setAllowanceTotal((int)sum);
	}
	
	/**
	 * 総控除金額を計算する。
	 */
	public void calcDeduction() {
		long sum = 0L;
		for (Iterator<DetailPdDto> it = deduction.iterator(); it.hasNext();) {
			sum += it.next().getAmount();
		}
		sum = sum < 0 ? 0 : sum;
		setSumAmount(PayrollConst.DTL_SUM_DEDUCTION, (int)sum);
		header.setDeductionTotal((int)sum);
	}
	
	/**
	 * 差引支給額を計算する。
	 */
	public void calcPaymentTotal() {
		long sum = 0L;
		sum = getAllowanceTotal() - getDeductionTotal();
		setSumAmount(PayrollConst.DTL_SUM_TOTAL, (int)sum);
		header.setPaymentTotal((int)sum);
	}
	
	/**
	 * 雇用保険対象額を計算する。
	 */
	public void calcUnemployInsuration() {
		long insureUnemploy = 0L;
		for (Iterator<DetailPaDto> it = allowance.iterator(); it.hasNext();) {
			DetailPaDto dto = it.next();
			if (dto.getUeInsType() == PayrollConst.DETAIL_UE_ADD) {
				insureUnemploy += dto.getAmount();
				continue;
			}
			if (dto.getUeInsType() == PayrollConst.DETAIL_UE_BREAK) {
				for (Iterator<DetailPbDto> itBreak =breakdown.iterator(); itBreak.hasNext();) {
					DetailPbDto dtoBreak = itBreak.next();
					if (dto.getDetailCode().equals(dtoBreak.getUpperCode())) {
						if (dtoBreak.getTotalType() == PayrollConst.DETAIL_TOTAL_UE) {
							insureUnemploy += dtoBreak.getAmount();
							continue;
						}
					}
				}
			}
		}
		insureUnemploy = insureUnemploy > 0 ? insureUnemploy : 0;
		header.setInsureUnemploy((int)insureUnemploy);
	}
	
	/**
	 * 課税対象額を計算する。
	 */
	public void calcTaxation() {
		long taxation = 0L;
		for (Iterator<DetailPaDto> it = allowance.iterator(); it.hasNext();) {
			DetailPaDto dto = it.next();
			if (dto.getTaxationType() == PayrollConst.DETAIL_TAX_ADD) {
				taxation += dto.getAmount();
				continue;
			}
			if (dto.getTaxationType() == PayrollConst.DETAIL_TAX_BREAK) {
				for (Iterator<DetailPbDto> itBreak =breakdown.iterator(); itBreak.hasNext();) {
					DetailPbDto dtoBreak = itBreak.next();
					if (dto.getDetailCode().equals(dtoBreak.getUpperCode())) {
						if (dtoBreak.getTotalType() == PayrollConst.DETAIL_TOTAL_TAX) {
							taxation += dtoBreak.getAmount();
							continue;
						}
					}
				}
			}
		}
		taxation = taxation > 0 ? taxation : 0;
		setSumAmount(PayrollConst.DTL_SUM_TAXATION, (int)taxation);
		header.setTaxation((int)taxation);
	}
	
	/**
	 * 扶養等の数を設定する。
	 * @param dependent 設定する扶養等の数
	 */
	public void setDependent(byte dependent) {
		header.setDependent(dependent);
	}
	
	/**
	 * 社会保険料等控除後の金額を取得する。
	 * @return 社会保険料等控除後の金額
	 */
	public int getAfterDeduction() {
		int afterDeduction = getTaxation() - getSocInsTotal();
		return afterDeduction > 0 ? afterDeduction : 0;
	}
	
	/**
	 * 単価設定項目を取得する。
	 * @return 単価設定項目配列
	 */
	public String[][] getUnitItem() {
		List<DetailDto> list = new ArrayList<DetailDto>();
		for (int i = 0; i < time.size(); i++) {
			DetailDto dto = time.get(i);
			if (dto.getInputType() == PayrollConst.DETAIL_IN_UNIT) {
				list.add(dto);
			}
		}
		for (int i = 0; i < allowance.size(); i++) {
			DetailDto dto = allowance.get(i);
			if (dto.getInputType() == PayrollConst.DETAIL_IN_UNIT) {
				list.add(dto);
			}
		}
		for (int i = 0; i < deduction.size(); i++) {
			DetailDto dto = deduction.get(i);
			if (dto.getInputType() == PayrollConst.DETAIL_IN_UNIT) {
				list.add(dto);
			}
		}
		for (int i = 0; i < breakdown.size(); i++) {
			DetailDto dto = breakdown.get(i);
			if (dto.getInputType() == PayrollConst.DETAIL_IN_UNIT) {
				list.add(dto);
			}
		}
		for (int i = 0; i < sum.size(); i++) {
			DetailDto dto = sum.get(i);
			if (dto.getInputType() == PayrollConst.DETAIL_IN_UNIT) {
				list.add(dto);
			}
		}
		return getAryDetail(list, false);
	}
	
}
