<?php

namespace MPHB\Entities;

use MPHB\Utils\DateUtils;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class Rate {

	/**
	 *
	 * @var int
	 */
	private $id;

	/**
	 *
	 * @var int
	 */
	private $originalId;

	/**
	 *
	 * @var string
	 */
	private $title;

	/**
	 *
	 * @var string
	 */
	private $description;

	/**
	 *
	 * @var int
	 */
	private $roomTypeId;

	/**
	 *
	 * @var SeasonPrice[]
	 */
	private $seasonPrices;

	/**
	 *
	 * @var bool
	 */
	private $isActive = false;

	/**
	 * Available dates with prices calculated based on MPHB()->reservationRequest().
	 * Get this field with $this->getDatePrices() method only to make sure we do not
	 * break the PriceLabs integration!
	 *
	 * @var array [ date ('Y-m-d') => price (float) ]
	 */
	private $datePrices = array();

	/**
	 *
	 * @param array         $atts Array of atts
	 * @param int           $atts['id'] Id of rate
	 * @param string        $atts['title'] Title of rate
	 * @param string        $atts['description'] Description of rate
	 * @param int           $atts['room_type_id'] Room Type ID
	 * @param SeasonPrice[] $atts['season_prices'] Array of Season Prices.
	 * @param bool          $atts['active'] Is rate available for user choosing.
	 */
	function __construct( array $atts ) {

		$this->id           = $atts['id'];
		$this->originalId   = MPHB()->translation()->getOriginalId( $this->id, MPHB()->postTypes()->rate()->getPostType() );
		$this->title        = $atts['title'];
		$this->description  = $atts['description'];
		$this->roomTypeId   = (int) $atts['room_type_id'];
		$this->seasonPrices = array_reverse( $atts['season_prices'] );
		$this->isActive     = $atts['active'];
	}

	/**
	 *
	 * @return int Id of rate
	 */
	public function getId() {
		return $this->id;
	}

	/**
	 * @return int
	 */
	public function getOriginalId() {
		return $this->originalId;
	}

	/**
	 *
	 * @return string Title
	 */
	public function getTitle() {
		return $this->title;
	}

	/**
	 *
	 * @return string Description
	 */
	public function getDescription() {
		return $this->description;
	}

	/**
	 *
	 * @return SeasonPrice[] Array of season prices.
	 */
	public function getSeasonPrices() {
		return $this->seasonPrices;
	}

	/**
	 * @return int original room type id
	 */
	public function getRoomTypeId() {
		return $this->roomTypeId;
	}

	/**
	 * @return bool
	 */
	public function isAvailableForDates( \DateTime $dateFrom, \DateTime $dateTo, $isIncludeLastDate = false ) {

		$checkingDate = clone $dateFrom;
		$checkingDate->setTime( 0, 0, 0 );

		$dateTo = clone $dateTo;
		$dateTo->setTime( 0, 0, 0 );

		if ( $isIncludeLastDate ) {
			$dateTo = $dateTo->modify( '+1 day' );
		}

		while ( $checkingDate < $dateTo ) {

			$checkingDateString = DateUtils::formatDateDB( $checkingDate );

			if ( ! array_key_exists( $checkingDateString, $this->getDatePrices() ) ) {
				return false;
			}

			$checkingDate = $checkingDate->modify( '+1 day' );
		}

		return true;
	}

	/**
	 * @return bool
	 */
	public function isExistsFrom( \DateTime $fromDate ) {
		$fromDateString = DateUtils::formatDateDB( $fromDate );

		foreach ( array_keys( $this->getDatePrices() ) as $date ) {
			if ( $date > $fromDateString ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * @return array [ date (string Y-m-d) => price (float) ]
	 */
	public function getDatePrices() {

		if ( empty( $this->datePrices ) ) {

			// Note: $seasonPrices are in reverse order (see array_reverse())
			foreach ( $this->seasonPrices as $seasonPrice ) {
				$this->datePrices = array_merge( $this->datePrices, $seasonPrice->getDatePrices() );
			}

			/**
			 * @param array $datePrices <code>[ Date string ("Y-m-d") => Price (float) ]</code>
			 * @param Rate $rate
			 */
			$this->datePrices = apply_filters( 'mphb_get_rate_date_prices', $this->datePrices, $this );
		}

		return $this->datePrices;
	}

	/**
	 * Does not recalculate prices, unlike <code>getPriceBreakdown()</code>.
	 *
	 * @since 6.0.0
	 *
	 * @param string|\DateTime $dateFrom
	 * @param string|\DateTime $dateTo
	 * @return array <code>[ Date string (Y-m-d) => Price (float) ]</code>
	 */
	public function getDatePricesForPeriod( $dateFrom, $dateTo ): array {
		$period = DateUtils::createDatePeriod( $dateFrom, $dateTo );
		$prices = array();

		$datePrices = $this->getDatePrices();

		foreach ( $period as $date ) {
			$dateStr = DateUtils::formatDateDB( $date );

			if ( array_key_exists( $dateStr, $datePrices ) ) {
				$prices[ $dateStr ] = $datePrices[ $dateStr ];
			}
		}

		return $prices;
	}

	/**
	 *
	 * @return bool
	 */
	public function isActive() {
		return $this->isActive;
	}

	/**
	 * Is it intended for editing? Can we change this event in the admin
	 * calendar?
	 *
	 * True for rates like Pricelabs_Rate.
	 *
	 * @since 6.0.0
	 */
	public function isReadonly(): bool {
		return false;
	}

	/**
	 *
	 * @return Season[]
	 */
	public function getSeasons() {
		$seasons = array_map(
			function( SeasonPrice $seasonPrice ) {
				return $seasonPrice->getSeason();
			},
			$this->seasonPrices
		);
		return array_filter( $seasons );
	}

	/**
	 * @param \DateTime|null $fromDate
	 * @param \DateTime|null $toDate
	 * @return float
	 */
	public function getMinBasePrice( $fromDate = null, $toDate = null ) {

		$useFilter = false;

		if ( $fromDate && is_a( $fromDate, '\DateTime' ) ) {
			$useFilter = true;
			$fromDate  = $fromDate->format( 'Y-m-d' );
		}

		if ( $toDate && is_a( $toDate, '\DateTime' ) ) {
			$useFilter = true;
			$toDate    = $toDate->format( 'Y-m-d' );
		}

		$datePrices = array();

		if ( $useFilter ) {
			foreach ( $this->getDatePrices() as $date => $price ) {

				if ( $fromDate && $date < $fromDate ) {
					continue;
				}

				if ( $toDate && $date > $toDate ) {
					continue;
				}

				$datePrices[ $date ] = $price;
			}
		} else {
			$datePrices = $this->getDatePrices();
		}

		return ! empty( $datePrices ) ? min( $datePrices ) : 0.0;
	}

	/**
	 * @param \DateTime $checkInDate
	 * @param \DateTime $checkOutDate
	 * @return float
	 *
	 * @since 3.5.0 removed optional parameter $occupancyParams.
	 */
	public function calcPrice( \DateTime $checkInDate, \DateTime $checkOutDate ) {
		return (float) array_sum( $this->getPriceBreakdown( $checkInDate, $checkOutDate ) );
	}

	/**
	 * Recalculates prices, unlike <code>getDatePricesForPeriod()</code>.
	 *
	 * @since 3.5.0 removed optional parameter $occupancyParams.
	 *
	 * @param \DateTime|string $checkInDate
	 * @param \DateTime|string $checkOutDate
	 * @return array Array where keys are dates and values are prices
	 */
	public function getPriceBreakdown( $checkInDate, $checkOutDate ) {

		$prices = array();

		// we need to recalculate prices each time for price breakdown MPI-11939
		$this->datePrices = array();
		$datePrices = $this->getDatePrices();

		foreach ( DateUtils::createDatePeriod( $checkInDate, $checkOutDate ) as $date ) {
			$dateDB = DateUtils::formatDateDB( $date );
			if ( array_key_exists( $dateDB, $datePrices ) ) {
				$prices[ $dateDB ] = $datePrices[ $dateDB ];
			}
		}

		return $prices;
	}

}
