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

use SabaiApps\Directories\Component\Field;
use SabaiApps\Directories\Application;
use SabaiApps\Directories\Component\Entity\Type\IEntity;
use SabaiApps\Directories\Component\Entity\Model\Bundle;

class FieldType extends Field\Type\AbstractType implements
	Field\Type\ISortable,
	Field\Type\IColumnable,
	Field\Type\IQueryable,
	Field\Type\IConditionable,
	Field\Type\ISchemable,
	Field\Type\IComparable {

	protected function _fieldTypeInfo() {
		return array(
			'label'      => __( 'Votes', 'directories-pro' ),
			'creatable'  => false,
			'icon'       => 'fas fa-star',
			'admin_only' => true,
		);
	}

	public function fieldTypeSchema( array $settings = array() ) {
		return array(
			'columns' => array(
				'count'         => array(
					'type'    => Application::COLUMN_INTEGER,
					'notnull' => true,
					'was'     => 'count',
					'default' => 0,
					'length'  => 10,
				),
				'sum'           => array(
					'type'     => Application::COLUMN_DECIMAL,
					'notnull'  => true,
					'length'   => 5,
					'scale'    => 2,
					'unsigned' => false,
					'was'      => 'sum',
					'default'  => 0,
				),
				'average'       => array(
					'type'     => Application::COLUMN_DECIMAL,
					'notnull'  => true,
					'length'   => 5,
					'scale'    => 2,
					'unsigned' => false,
					'was'      => 'average',
					'default'  => 0,
				),
				'last_voted_at' => array(
					'type'    => Application::COLUMN_INTEGER,
					'notnull' => true,
					'was'     => 'last_voted_at',
					'default' => 0,
					'length'  => 10,
				),
				'name'          => array(
					'type'    => Application::COLUMN_VARCHAR,
					'length'  => 40,
					'notnull' => true,
					'was'     => 'name',
					'default' => '',
				),
				'count_init'    => array(
					'type'     => Application::COLUMN_INTEGER,
					'notnull'  => true,
					'unsigned' => false,
					'was'      => 'count_init',
					'default'  => 0,
					'length'   => 10,
				),
				'sum_init'      => array(
					'type'     => Application::COLUMN_DECIMAL,
					'notnull'  => true,
					'length'   => 5,
					'scale'    => 2,
					'unsigned' => false,
					'was'      => 'sum_init',
					'default'  => 0,
				),
				'level'         => array(
					'type'    => Application::COLUMN_INTEGER,
					'notnull' => true,
					'was'     => 'level',
					'default' => 0,
					'length'  => 10,
				),
			),
			'indexes' => array(
				'count'         => array(
					'fields' => array( 'count' => array( 'sorting' => 'ascending' ) ),
					'was'    => 'count',
				),
				'sum'           => array(
					'fields' => array( 'sum' => array( 'sorting' => 'ascending' ) ),
					'was'    => 'sum',
				),
				'average'       => array(
					'fields' => array( 'average' => array( 'sorting' => 'ascending' ) ),
					'was'    => 'average',
				),
				'last_voted_at' => array(
					'fields' => array( 'last_voted_at' => array( 'sorting' => 'ascending' ) ),
					'was'    => 'last_voted_at',
				),
				'name'          => array(
					'fields' => array( 'name' => array( 'sorting' => 'ascending' ) ),
					'was'    => 'name',
				),
				'level'         => array(
					'fields' => array( 'level' => array( 'sorting' => 'ascending' ) ),
					'was'    => 'level',
				),
			),
		);
	}

	public function fieldTypeOnSaveValues( Field\IField $field, array $values, ?array $currentValues = null, array &$extraArgs = array() ) {
		$ret = array();
		foreach ( $values as $value ) {
			if ( ! is_array( $value ) ) {
				continue;
			}

			if ( ! isset( $value['name'] )
				|| ! strlen( trim( $value['name'] ) )
			) {
				$value['name'] = '';
			}
			if ( empty( $value['count_init'] ) ) {
				$value['count_init'] = $value['sum_init'] = 0;
			}
			if ( isset( $currentValues[0][ $value['name'] ] ) ) {
				if ( empty( $value['force'] ) ) { // used by Faker generator to force count_init/sum_init
					// The following values may not be updated
					if ( isset( $currentValues[0][ $value['name'] ]['count_init'] ) ) {
						$value['count_init'] = (int) $currentValues[0][ $value['name'] ]['count_init'];
					}
					if ( isset( $currentValues[0][ $value['name'] ]['sum_init'] ) ) {
						$value['sum_init'] = (float) $currentValues[0][ $value['name'] ]['sum_init'];
					}
				} else {
					unset( $currentValues[0][ $value['name'] ] );
				}
			}

			// Increment count/sum
			if ( ! empty( $value['count_init'] ) ) {
				$value['count'] += $value['count_init'];
			}
			if ( ! empty( $value['sum_init'] ) ) {
				$value['sum'] += $value['sum_init'];
			}

			if ( empty( $value['count'] ) ) {
				continue; // no votes
			}

			if ( empty( $value['sum'] ) ) {
				$value['average'] = 0.00;
				$value['level']   = 0;
			} else {
				$value['average'] = (float) round( $value['sum'] / $value['count'], 2 );
				$value['level']   = (int) round( $value['average'] );
			}

			$ret[ $value['name'] ] = $value;
		}

		if ( empty( $ret ) ) {
			if ( isset( $values[0] )
				&& $values[0] === false
			) {
				return array( false );
			}
			if ( ! empty( $currentValues[0] ) ) {
				// Preserve current entry if count_init is configured
				foreach ( $currentValues[0] as $name => $current ) {
					if ( empty( $current['count_init'] ) ) {
						continue;
					}

					$ret[ $name ] = array(
						'count'   => (int) $current['count_init'],
						'sum'     => (float) $current['sum_init'],
						'average' => (float) round( $current['sum_init'] / $current['count_init'], 2 ),
					) + $current;
				}
			}
		}

		return array_values( $ret );
	}

	public function fieldTypeOnLoadValues( Field\IField $field, array &$values, IEntity $entity, array $allValues ) {
		$new_values = array();
		foreach ( $values as $value ) {
			// Index by vote name
			$_value = $value;
			unset( $_value['name'] );
			$new_values[ $value['name'] ] = $_value;
		}
		$values = array( $new_values );
	}

	public function fieldTypeIsModified( Field\IField $field, $valueToSave, $currentLoadedValue ) {
		if ( empty( $currentLoadedValue[0] ) ) {
			return true;
		}

		$current = array();
		foreach ( array_keys( $currentLoadedValue[0] ) as $name ) {
			$current[] = $currentLoadedValue[0][ $name ] + array( 'name' => $name );
		}
		return $current !== $valueToSave;
	}

	public function fieldSortableOptions( Field\IField $field ) {
		$field_name = $field->getFieldName();
		if ( strpos( $field_name, 'voting_' ) === 0 ) {
			$type = substr( $field_name, 7/*strlen('voting_')*/ );
			if ( ! $type_impl = $this->_application->Voting_Types_impl( $type, true ) ) {
				return false;
			}

			if ( ! $label = $type_impl->votingTypeInfo( 'label_sort' ) ) {
				$label = $type_impl->votingTypeInfo( 'label' );
			}
		} else {
			$label = $field->getFieldLabel( true );
		}
		return array(
			array( 'label' => $label ),
			array(
				'args'  => array( 'asc' ),
				'label' => sprintf( __( '%s (asc)', 'directories-pro' ), $label ),
			),
		);
	}

	public function fieldSortableSort( Field\Query $query, $fieldName, ?array $args = null ) {
		$extra_column = $extra_column_value = null;
		switch ( $fieldName ) {
			case 'voting_updown':
				$column = 'sum';
				break;
			case 'voting_bookmark':
				$column = 'count';
				break;
			case 'voting_rating':
				$column = 'average';
				break;
			default:
				// Todo call Voting_Type and then let the implementation set additional criteria
				$column             = 'average';
				$extra_column       = 'name';
				$extra_column_value = '_all';
		}
		$query->sortByField(
			$fieldName,
			isset( $args ) && $args[0] === 'asc' ? 'ASC' : 'DESC',
			$column,
			null,
			0,
			$extra_column,
			$extra_column_value
		);
	}

	public function fieldQueryableInfo( Field\IField $field, $inAdmin = false ) {
		return array(
			'example' => '1,10',
			'tip'     => __( 'Enter a single number for exact match, two numbers separated with a comma for range search, or "none".', 'directories-pro' ),
		);
	}

	public function fieldQueryableQuery( Field\Query $query, $fieldName, $paramStr, Bundle $bundle ) {
		switch ( $fieldName ) {
			case 'voting_updown':
				$column = 'sum';
				break;
			case 'voting_bookmark':
				$column = 'count';
				break;
			case 'voting_rating':
				$column = 'average';
				break;
			default:
				// Todo call Voting_Type and then let the implementation set additional criteria
				$query->fieldIs( $fieldName, '_all', 'name' );
				$column = 'average';
		}
		$params = $this->_queryableParams( $paramStr, false );
		switch ( count( $params ) ) {
			case 0:
				break;
			case 1:
				if ( strlen( $params[0] ) ) {
					if ( $params[0] === 'none' ) {
						$query->fieldIsNull( $fieldName, $column );
					} else {
						$query->fieldIs( $fieldName, $params[0], $column );
					}
				}
				break;
			default:
				if ( strlen( $params[0] ) ) {
					$query->fieldIsOrGreaterThan( $fieldName, $params[0], $column );
				}
				if ( strlen( $params[1] ) ) {
					$query->fieldIsOrSmallerThan( $fieldName, $params[1], $column );
				}
		}
	}

	public function fieldColumnableInfo( Field\IField $field ) {
		if ( strpos( $field->getFieldName(), 'voting_' ) !== 0 ) {
			return;
		}

		$type = substr( $field->getFieldName(), 7/*strlen('voting_')*/ );
		if ( ! $type_impl = $this->_application->Voting_Types_impl( $type, true ) ) {
			return;
		}

		$type_info = $type_impl->votingTypeInfo();
		return array(
			'' => array(
				'icon'   => $type_info['icon'],
				'label'  => $type_info['label'],
				'sortby' => 'count',
			),
		);
	}

	public function fieldColumnableColumn( Field\IField $field, IEntity $entity, $column = '' ) {
		if ( ( ! $values = $entity->getFieldValue( $field->getFieldName() ) )
			|| empty( $values[0] )
		) {
			return;
		}

		$type = substr( $field->getFieldName(), 7/*strlen('voting_')*/ );
		if ( ! $type_impl = $this->_application->Voting_Types_impl( $type, true ) ) {
			return;
		}

		if ( isset( $values[0][''] ) ) {
			$value = $values[0][''];
		} else {
			$value = array(
				'count'   => 0,
				'sum'     => 0,
				'average' => 0,
				'level'   => 0,
			);
		}
		return $type_impl->votingTypeFormat( $value, 'column' );
	}

	public function fieldConditionableInfo( Field\IField $field, $isServerSide = false ) {
		if ( ! $isServerSide ) {
			return;
		}

		$ret = array(
			'count' => array(
				'label'   => $field->getFieldLabel(),
				'compare' => array( '<value', '>value', '<>value', 'empty', 'filled' ),
				'tip'     => __( 'Enter a single numeric value or multiple numeric values separated with a comma', 'directories-pro' ),
				'example' => '10',
			),
		);
		if ( in_array(
			$field->getFieldName(),
			array(
				'voting_rating',
				'review_ratings',
			)
		) ) {
			$ret['count']['label'] .= ' - ' . _x( 'Count', 'rating count', 'directories-pro' );
			$ret['average']         = array(
				'label'   => $field->getFieldLabel(),
				'compare' => array( '<value', '>value', '<>value' ),
				'tip'     => __( 'Enter a single numeric value or multiple numeric values separated with a comma', 'directories-pro' ),
				'example' => '3.5,5',
			);
		}
		return $ret;
	}

	public function fieldConditionableRule( Field\IField $field, $compare, $value = null, $name = '' ) {
		switch ( $compare ) {
			case '<value':
			case '>value':
				return is_numeric( $value ) ? array(
					'type'  => $compare,
					'value' => $value,
				) : null;
			case '<>value':
				if ( strpos( $value, ',' )
					&& ( $values = explode( ',', $value ) )
					&& is_numeric( $values[0] )
					&& is_numeric( $values[1] )
				) {
					return array(
						'type'  => $compare,
						'value' => $values[0] . ',' . $values[1],
					);
				}
			case 'empty':
				return array(
					'type'  => 'filled',
					'value' => false,
				);
			case 'filled':
				return array(
					'type'  => 'empty',
					'value' => false,
				);
			default:
		}
	}

	public function fieldConditionableMatch( Field\IField $field, array $rule, array $values, IEntity $entity ) {
		switch ( $rule['type'] ) {
			case '<value':
			case '>value':
				if ( ! empty( $values ) ) {
					$key1 = '_all';
					$key2 = $rule['name'];
					foreach ( $values as $input ) {
						foreach ( (array) $rule['value'] as $rule_value ) {
							if ( $rule['type'] === '<value' ) {
								if ( $input[ $key1 ][ $key2 ] < $rule_value ) {
									return true;
								}
							} elseif ( $input[ $key1 ][ $key2 ] > $rule_value ) {
								return true;
							}
						}
					}
				}
				return false;
			case '<>value':
				if ( ! empty( $values ) ) {
					$key1 = '_all';
					$key2 = $rule['name'];
					foreach ( $values as $input ) {
						foreach ( (array) $rule['value'] as $rule_value ) {
							if ( ! strpos( $rule_value, ',' )
								|| ( ! $rule_value = explode( ',', $rule_value ) )
								|| count( $rule_value ) < 2
							) {
								continue;
							}

							if ( $input[ $key1 ][ $key2 ] >= $rule_value[0]
								&& $input[ $key1 ][ $key2 ] <= $rule_value[1]
							) {
								return true;
							}
						}
					}
				}
				return false;
			case 'empty':
				return empty( $values ) === $rule['value'];
			case 'filled':
				return ! empty( $values ) === $rule['value'];
			default:
				return false;
		}
	}

	public function fieldSchemaProperties( Field\IField $field ) {
		switch ( $field->getFieldName() ) {
			case 'voting_rating':
			case 'review_ratings':
				return array( 'aggregateRating' );
			default:
				return array();
		}
	}

	public function fieldSchemaRenderProperty( Field\IField $field, $property, IEntity $entity ) {
		if ( ! $value = $entity->getSingleFieldValue( $field->getFieldName() ) ) {
			return;
		}

		$key = $field->getFieldName() === 'review_ratings' ? '_all' : '';
		if ( empty( $value[ $key ]['count'] )
			|| ! isset( $value[ $key ]['average'] )
		) {
			return;
		}

		return array(
			array(
				'@type'       => 'AggregateRating',
				'ratingValue' => $value[ $key ]['average'],
				'reviewCount' => $value[ $key ]['count'],
				'bestRating'  => 5,
				'worstRating' => 0,
			),
		);
	}

	public function fieldComparableItemLabels( Field\IField $field ) {
		if ( strpos( $field->getFieldName(), 'voting_' ) == 0 ) {
			$type = substr( $field->getFieldName(), 7/*strlen('voting_')*/ );
			if ( $type_impl = $this->_application->Voting_Types_impl( $type, true ) ) {
				return array( '' => $type_impl->votingTypeInfo( 'label' ) );
			}
		}
		if ( $field->getFieldName() === 'review_ratings' ) {
			return array( '' => __( 'Review Rating', 'directories-pro' ) );
		}

		return array();
	}

	public function fieldComparableItemValues( Field\IField $field, IEntity $entity, $html = true ) {
		if ( ! $value = $entity->getSingleFieldValue( $field->getFieldName() ) ) {
			return array( '' => '' );
		}

		if ( strpos( $field->getFieldName(), 'voting_' ) === 0 ) {
			if ( empty( $value[''] ) ) {
				return array( '' => '' );
			}

			$type = substr( $field->getFieldName(), 7/*strlen('voting_')*/ );
			if ( ! $type_impl = $this->_application->Voting_Types_impl( $type, true ) ) {
				return array( '' => '' );
			}

			return array( '' => $type_impl->votingTypeFormat( $value[''], 'column' ) );
		}

		if ( $field->getFieldName() === 'review_ratings' ) {
			if ( $html ) {
				return array( '' => $this->_application->Entity_Field_render( $entity, $field->getFieldName(), 'review_ratings' ) );
			}
			return array( '' => $this->_application->Review_RenderRating_text( $value ) );
		}
	}
}
