<?php
namespace SabaiApps\Directories\Component\Voting\Helper;

use SabaiApps\Directories\Application;
use SabaiApps\Directories\Component\Entity\Type\IEntity;
use SabaiApps\Directories\Component\Entity\Model\Bundle;
use SabaiApps\Directories\Exception;
use SabaiApps\Directories\Component\Voting\Exception\NoValidVoteFoundException;
use SabaiApps\Framework\Application\HttpRequest;

class VotesHelper {

	protected $_votes = array();

	public function help( Application $application, $entityId = null, $type = null, $userId = null ) {
		if ( ! isset( $userId ) ) {
			if ( $application->getUser()->isAnonymous() ) {
				return;
			}

			$userId = $application->getUser()->id;
		}

		if ( ! isset( $this->_votes[ $userId ] ) ) {
			return;
		}

		if ( isset( $entityId ) ) {
			if ( $entityId instanceof IEntity ) {
				$entityId = $entityId->getId();
			}

			if ( ! isset( $this->_votes[ $userId ][ $entityId ] ) ) {
				return;
			}

			if ( ! isset( $type ) ) {
				return $this->_votes[ $userId ][ $entityId ];
			}

			return isset( $this->_votes[ $userId ][ $entityId ][ 'voting_' . $type ] ) ? $this->_votes[ $userId ][ $entityId ][ 'voting_' . $type ] : null;
		}
		return $this->_votes;
	}

	public function load( Application $application, $bundle, array $entityIds ) {
		if ( $application->getUser()->isAnonymous() ) {
			return;
		}

		$user_id = $application->getUser()->id;

		$entity_ids = array();
		foreach ( $entityIds as $entity_id ) {
			if ( isset( $this->_votes[ $user_id ][ $entity_id ] ) ) {
				continue;
			}

			$this->_votes[ $user_id ][ $entity_id ] = array();
			$entity_ids[]                           = $entity_id;
		}

		if ( empty( $entity_ids ) ) {
			return;
		}

		$votes = $application->getModel( null, 'Voting' )->getGateway( 'Vote' )->getVotes(
			$bundle instanceof Bundle ? $bundle->name : $bundle,
			$entity_ids,
			$user_id,
			array( 'voting_updown', 'voting_rating', 'voting_bookmark' )
		);
		foreach ( $votes as $field_name => $_votes ) {
			foreach ( $_votes as $entity_id => $value ) {
				$this->_votes[ $user_id ][ $entity_id ][ $field_name ] = $value;
			}
		}
	}

	public function recalculate( Application $application, IEntity $entity, $fieldName ) {
		// Make sure all the fields are loaded
		$application->Entity_Field_load( $entity, null, false, false );

		// Calculate results
		$results = $application->getModel( null, 'Voting' )
			->getGateway( 'Vote' )
			->getResults( $entity->getBundleName(), $entity->getId(), $fieldName );

		// Field values for the entity
		if ( ! empty( $results ) ) {
			$values = array();
			foreach ( array_keys( $results ) as $name ) {
				$values[] = array( 'name' => $name ) + $results[ $name ];
			}
		} else {
			$values = false;
		}

		// Update field and return its value
		return $application->Entity_Save( $entity, array( $fieldName => $values ) )->getSingleFieldValue( $fieldName );
	}

	public function cast( Application $application, IEntity $entity, $fieldName, array $values, array $options = array() ) {
		$default = array(
			'comment'        => '',
			'user_id'        => null,
			'reference_id'   => null, // required for edit/delte vote
			'delete'         => false,
			'edit'           => false,
			'allow_empty'    => false,
			'allow_multiple' => false,
			'min'            => null,
			'max'            => null,
			'step'           => null,
		);

		if ( strpos( $fieldName, 'voting_' ) === 0 ) {
			$vote_type = substr( $fieldName, 7 );
			$type_impl = $application->Voting_Types_impl( $vote_type );
			if ( ! $type_impl->votingTypeIsEnabled()
				|| ( ! $vote_types_enabled = $application->Entity_BundleTypeInfo( $entity->getBundleType(), 'voting_enable' ) )
				|| ! in_array( $vote_type, $vote_types_enabled )
			) {
				return;
			}

			$default['allow_empty']    = (bool) $type_impl->votingTypeInfo( 'allow_empty' );
			$default['allow_multiple'] = (bool) $type_impl->votingTypeInfo( 'allow_multiple' );
			$default['min']            = $type_impl->votingTypeInfo( 'min' );
			$default['max']            = $type_impl->votingTypeInfo( 'max' );
			$default['step']           = $type_impl->votingTypeInfo( 'step' );
		}

		$options   += $default;
		$new_values = $prev_values = array();

		$user_id = isset( $options['user_id'] ) ? $options['user_id'] : $application->getUser()->id;
		$hash    = $this->_getHash( $application, $entity->getId(), $fieldName, $user_id, $options['reference_id'] );

		$valid_vote_found = false;

		if ( empty( $options['allow_multiple'] ) ) {
			$votes = $this->current( $application, $entity->getBundleName(), $entity->getId(), $fieldName, $user_id, $options['reference_id'], $hash );
			foreach ( $values as $name => $value ) {
				$new_values[ $name ] = $value;
				if ( isset( $votes[ $name ] ) ) {
					// Has voted before
					$prev_values[ $name ] = $votes[ $name ]->value;
					if ( ! $this->_isValidVote( $options, $value )
						|| ( $votes[ $name ]->value == $value && ! $options['edit'] ) // value has not changed
					) {
						// Undo vote by deleting
						$votes[ $name ]->markRemoved();
						$new_values[ $name ] = false;
					} else {
						// Update vote
						$votes[ $name ]->value   = $value;
						$votes[ $name ]->level   = round( $value );
						$votes[ $name ]->comment = $options['comment'];
					}
					$valid_vote_found = true;
				} elseif ( ! $options['delete'] ) {
					if ( ! $this->_isValidVote( $options, $value ) ) {
						continue;
					}

					$prev_values[ $name ] = false;
					// New vote
					$votes[ $name ]   = $this->_createVote( $application, $entity->getBundleName(), $entity->getId(), $fieldName, $value, $user_id, $options['comment'], $name, $options['reference_id'], $hash );
					$valid_vote_found = true;
				}
			}
			foreach ( array_diff( array_keys( $votes ), array_keys( $values ) ) as $vote_removed_key ) {
				$votes[ $vote_removed_key ]->markRemoved();
				$valid_vote_found = true;
			}
		} else {
			$votes = array();
			foreach ( $values as $name => $value ) {
				if ( ! $this->_isValidVote( $options, $value ) ) {
					continue;
				}

				$new_values[ $name ]  = $value;
				$prev_values[ $name ] = false;
				// New vote
				$votes[ $name ]   = $this->_createVote( $application, $entity->getBundleName(), $entity->getId(), $fieldName, $value, $user_id, $options['comment'], $name, null, $hash );
				$valid_vote_found = true;
			}
		}

		if ( ! $valid_vote_found ) {
			throw new NoValidVoteFoundException( __( 'No valid vote found.', 'directories-pro' ) );
		}

		$application->getModel( null, 'Voting' )->commit();

		// Calculate results and update entity
		$results = $this->recalculate( $application, $entity, $fieldName );

		foreach ( array_keys( $votes ) as $name ) {
			if ( ! isset( $results[ $name ] ) ) {
				$results[ $name ] = array(
					'count'         => 0,
					'sum'           => '0.00',
					'last_voted_at' => 0,
				);
			}
			$results[ $name ]['value']      = $new_values[ $name ];
			$results[ $name ]['prev_value'] = $prev_values[ $name ];
			unset( $votes[ $name ] );
		}

		// Notify voted
		$application->Action( 'voting_entity_voted', array( $entity, $fieldName, $results ) );

		return $results;
	}

	protected function _getHash( Application $application, $entityId, $fieldName, $userId, $referenceId ) {
		// Require a valid ip address for anonymous votes
		if ( empty( $userId ) && empty( $referenceId ) ) {
			if ( ! $ip = HttpRequest::ip( '' ) ) {
				throw new Exception\RuntimeException( __( 'You do not have the permission to perform this action.', 'directories-pro' ) );
			}
			return $application->getPlatform()->getHash( $entityId . $fieldName . $ip );
		}
		return '';
	}

	public function current( Application $application, $bundleName, $entityId, $fieldName, $userId = null, $referenceId = null, $hash = null ) {
		if ( ! isset( $userId ) ) {
			$userId = $application->getUser()->id;
		}
		$votes = $application->getModel( 'Vote', 'Voting' )
			->bundleName_is( $bundleName )
			->entityId_is( $entityId )
			->fieldName_is( $fieldName );
		if ( ! empty( $referenceId ) ) {
			$votes->referenceId_is( $referenceId );
		} else {
			$votes->userId_is( $userId );
		}
		if ( ! isset( $hash ) ) {
			$hash = $this->_getHash( $application, $entityId, $fieldName, $userId, $referenceId );
		}
		if ( $hash ) {
			$votes->hash_is( $hash );
		}
		return $votes->fetch( 'created', 'DESC' )->getArray( null, 'name' );
	}

	/**
	 * @param array $settings
	 * @param mixed $value
	 * @return bool
	 */
	protected function _isValidVote( array $settings, $value ) {
		if ( empty( $value ) && ! $settings['allow_empty'] ) {
			return false;
		}
		if ( ! is_numeric( $value ) ) {
			return false;
		}
		if ( ( isset( $settings['max'] ) && $value > $settings['max'] )
			|| ( isset( $settings['min'] ) && $value < $settings['min'] )
		) {
			return false;
		}
		if ( isset( $settings['step'] )
			&& intval( strval( $value * 100 ) ) % intval( strval( $settings['step'] * 100 ) ) !== 0 // avoid using float numbers for % operation
		) {
			return false;
		}
		return true;
	}

	private function _createVote( Application $application, $bundleName, $entityId, $fieldName, $value, $userId, $comment, $name, $referenceId = null, $hash = '' ) {
		$vote               = $application->getModel( null, 'Voting' )->create( 'Vote' )->markNew();
		$vote->bundle_name  = $bundleName;
		$vote->entity_id    = $entityId;
		$vote->field_name   = $fieldName;
		$vote->user_id      = $userId;
		$vote->value        = $value;
		$vote->comment      = $comment;
		$vote->name         = $name;
		$vote->reference_id = $referenceId;
		$vote->hash         = $hash;
		$vote->level        = round( $value );

		return $vote;
	}
}
