<?php
namespace SabaiApps\Directories\Component\View\Controller;

use SabaiApps\Directories\Controller;
use SabaiApps\Directories\Context;
use SabaiApps\Directories\Component\Entity;
use SabaiApps\Directories\Exception\RuntimeException;

abstract class AbstractQueryEntities extends Controller {

	protected $_settings, $_settingsCacheId;
	protected static $_defaultSettings = array();

	protected function _doExecute( Context $context ) {
		// Notify
		$this->Action( 'view_init', array( $context ) );

		$this->_init( $context );

		if ( $context->isError() ) {
			return;
		}

		// Notify
		$this->Action( 'view_query_entities', array( $context ) );

		// Filter
		$this->_filter( $context );

		// Sort and Query
		if ( empty( $context->settings['filter_only'] ) ) {
			// Show featured first?
			if ( ! empty( $context->settings['sort']['stick_featured'] )
				&& $this->Entity_BundleTypeInfo( $context->bundle, 'featurable' )
			) {
				if ( ! empty( $this->_settings['is_default_view'] )
					&& ! empty( $context->settings['sort']['stick_featured_term_only'] )
				) {
					// Only on single taxonomy term pages
					if ( isset( $GLOBALS['drts_entity'] )
						&& $GLOBALS['drts_entity']->isTaxonomyTerm()
					) {
						$context->query->sortByField( 'entity_featured', 'DESC', 'value', null, null, null, null, -99 )
							->sortByField( 'entity_featured', 'DESC', 'featured_at' );
					}
				} else {
					$context->query->sortByField( 'entity_featured', 'DESC', 'value', null, null, null, null, -99 )
						->sortByField( 'entity_featured', 'DESC', 'featured_at' );
				}
			}
			if ( isset( $context->sort ) ) {
				$query_sorts           = array( $context->sort );
				$query_sorts_available = $context->sorts;
				if ( ! empty( $context->settings['sort']['secondary'] )
					&& ( $secondary_sort = $context->settings['sort']['secondary'] )
					&& $context->sort !== $secondary_sort
					&& ( $available_sorts = $this->_getAvailableSorts( $context->bundle ) )
					&& isset( $available_sorts[ $secondary_sort ] )
				) {
					$query_sorts[]                            = $secondary_sort;
					$query_sorts_available[ $secondary_sort ] = $available_sorts[ $secondary_sort ];
				}
				$context->query->sort( $query_sorts, $query_sorts_available, $context->bundle->name . '-' . $context->view_name );
			}

			// Do query
			$save_found_entity_ids = isset( $context->filter['form'] ) && $this->_isFilterFacetCountEnabled( $context );
			$query_limit           = isset( $context->settings['query']['limit'] ) ? $context->settings['query']['limit'] : 0;
			if ( $context->perpage ) {
				$page               = $context->getRequest()->asInt( $this->getPlatform()->getPageParam(), 1 );
				$context->paginator = $paginator = $context->query->paginate( $context->perpage, $query_limit, null, true, $save_found_entity_ids )->setCurrentPage( $page );
				$context->entities  = $paginator->getElements();
				$context->num_found = $paginator->getElementCount();
				$context->num_shown = $paginator->getElementLimit();
				$context->num_start = $context->num_found ? $paginator->getElementOffset() + 1 : 0;
			} else {
				$context->entities  = $context->query->fetch( $query_limit, 0, null, true, $save_found_entity_ids );
				$context->num_found = $context->num_shown = count( $context->entities );
				$context->num_start = $context->num_found ? 1 : 0;
			}
		} else {
			$context->num_found = $context->query->count();
		}
	}

	protected function _init( Context $context ) {
		// Init
		if ( ! $bundle = $this->_getBundle( $context ) ) {
			$context->setError();
			return;
		}
		$context->bundle = $bundle;

		// Init settings and view
		try {
			$this->_settings = $this->_getSettings( $context ) + static::$_defaultSettings;
		} catch ( RuntimeException $e ) {
			$context->setError( $this->getUser()->isAdministrator() ? $e->getMessage() : null ); // hide system errors from users
			return;
		}
		$view_mode_name = isset( $this->_settings['mode'] ) ? $this->_settings['mode'] : 'list';
		if ( ! $view = $this->View_Modes_impl( $view_mode_name, $context->bundle, true ) ) {
			$context->setError( sprintf( 'Invalid view mode: %s.', $view_mode_name ) );
			return;
		}

		if ( ! isset( $this->_settings['view_settings'] ) ) {
			$this->_settings['view_settings'] = array();
		}
		$this->_settings['view_settings'] += $view->viewModeInfo( 'default_settings' );
		$view->viewModeOnInitSettings( $context->bundle, $this->_settings, $context->getRequest() );
		$this->_settings = $this->Filter( 'view_entities_settings', $this->_settings, array( $bundle, $view, $context->getRequest() ) );
		$view_settings   = $this->_settings['view_settings'];
		unset( $this->_settings['view_settings'] );

		// Init URL params
		$url_params = $this->_getUrlParams( $context );

		if ( empty( $view_settings['filter_only'] ) ) {
			// Init sorts
			$current_sort = null;
			if ( isset( $view_settings['sort'] )
				&& ( $sorts = (array) $this->Filter( 'view_entities_sorts', $this->_getSorts( $context, $view_settings['sort'] ), array( $bundle, $view, $view_settings, $context->getRequest() ) ) )
			) {
				$sort_keys          = array_keys( $sorts );
				$default_sort       = isset( $view_settings['sort']['default'] ) && isset( $sorts[ $view_settings['sort']['default'] ] ) ? $view_settings['sort']['default'] : array_shift( $sort_keys );
				$url_params['sort'] = $current_sort = $context->getRequest()->asStr( 'sort', $default_sort, $sort_keys );
			}
			$context->sort = $current_sort;

			// Init pagination
			$perpage = 0;
			if ( ! empty( $view_settings['pagination'] )
				&& empty( $view_settings['pagination']['no_pagination'] )
			) {
				if ( isset( $view_settings['pagination']['perpage'] ) ) {
					$perpage = (int) $view_settings['pagination']['perpage'];
				}
				if ( empty( $perpage ) ) {
					$perpage = 20;
				}
				if ( ! empty( $view_settings['pagination']['allow_perpage'] )
					&& ! empty( $view_settings['pagination']['perpages'] )
				) {
					$url_params['num'] = $perpage = $context->getRequest()->asInt( 'num', $perpage, $view_settings['pagination']['perpages'] );
				}
			}
			$context->perpage   = $perpage;
			$context->paginator = null;
		}

		// Has parent entity?
		if ( $parent_entity = $this->_hasParentEntity( $context ) ) {
			$url_params['parent_entity_id'] = $parent_entity->getId();
		}

		// Create query
		$query = $this->View_Query(
			$context->bundle,
			array(
				'mode'       => $this->_settings['mode'],
				'settings'   => $view_settings,
				'name'       => isset( $this->_settings['name'] ) ? $this->_settings['name'] : '',
				'is_default' => ! empty( $this->_settings['is_default_view'] ),
			)
		);
		// Showing child items?
		if ( $parent_entity ) {
			$query->fieldIs( 'parent', $parent_entity->getId() );
		}

		// Init context
		$context->setAttributes(
			array(
				'view_name'       => isset( $this->_settings['name'] ) ? $this->_settings['name'] : null,
				'view'            => $view,
				'is_default_view' => ! empty( $this->_settings['is_default_view'] ),
				'bundle'          => $bundle,
				'url_params'      => $url_params,
				'settings'        => $view_settings,
				'entities'        => array(),
				'filter'          => array(),
				'query'           => $query,
				'sorts'           => empty( $sorts ) ? array() : $sorts,
			)
		);
	}

	protected function _hasParentEntity( Context $context ) {
		if ( empty( $context->bundle->info['parent'] ) ) {
			return;
		}

		if ( isset( $context->entity ) ) {
			return $context->entity;
		}

		if ( ( ! $parent_entity_id = $context->getRequest()->asInt( 'parent_entity_id' ) )
			|| ( ! $parent_entity = $this->Entity_Entity( $context->bundle->entitytype_name, $parent_entity_id ) )
			|| $parent_entity->getBundleName() !== $context->bundle->info['parent']
		) {
			return;
		}

		return $parent_entity;
	}

	protected function _getSettings( Context $context ) {
		// Any custom settings provided or cached?
		if ( ( $this->_settingsCacheId = $context->getRequest()->asStr( 'settings_cache_id', $this->_settingsCacheId ) )
			&& ( $settings = $this->getPlatform()->getCache( $this->_settingsCacheId ) )
		) {
			// Reconstruct entity if inside another entity page
			if ( isset( $settings['_entity'] )
				&& $settings['_entity'] instanceof Entity\Type\IEntity
			) {
				$this->Entity_Field_load( $settings['_entity'] );
				$GLOBALS['drts_entity'] = $settings['_entity'];
			}
		} elseif ( ! empty( $context->settings ) ) {
				$settings = $context->settings;
				// Preserve entity if inside another entity page
			if ( isset( $GLOBALS['drts_entity'] )
					&& $GLOBALS['drts_entity'] instanceof Entity\Type\IEntity
				) {
				$settings['_entity'] = $GLOBALS['drts_entity'];
			}
				// Cache custom settings
				$this->_settingsCacheId = md5( get_class( $this ) . serialize( $settings ) );
				$this->getPlatform()->setCache( $settings, $this->_settingsCacheId );
		}
		if ( empty( $settings ) ) {
			$settings = array();
		}

		// Use default view settings if no settings defined or filter_only is set to true
		if ( empty( $settings )
			|| ! empty( $settings['filter_only'] )
		) {
			if ( $view = $this->getModel( 'View', 'View' )->bundleName_is( $context->bundle->name )->default_is( true )->fetchOne() ) {
				$settings = array(
					'name'            => $view->name,
					'mode'            => $view->mode,
					'view_settings'   => $view->data['settings'],
					'is_default_view' => true,
				) + $settings;
			} else {
				throw new RuntimeException( 'No view defined for bundle: ' . $context->bundle->name );
			}
		} else {
			// Merge settings of a specific view
			if ( isset( $settings['load_view'] ) ) {
				if ( ! $view = $this->getModel( 'View', 'View' )->bundleName_is( $context->bundle->name )->name_is( $settings['load_view'] )->fetchOne() ) {
					throw new RuntimeException( 'Invalid view name: ' . $settings['load_view'] );
				}
				unset( $settings['load_view'], $settings['name'], $settings['mode'] );
				$settings                    = array_replace_recursive(
					array(
						'name'          => $view->name,
						'mode'          => $view->mode,
						'view_settings' => $view->data['settings'],
					),
					$settings
				);
				$settings['is_default_view'] = $view->isDefault();
			} elseif ( ! isset( $settings['name'] )
					&& ! isset( $settings['mode'] )
				) {

					throw new RuntimeException( 'Invalid view name or mode for bundle: ' . $context->bundle->name );
			}
		}
		// Disable push_state if showing on parent entity page.
		if ( ! empty( $context->bundle->info['parent'] )
			&& isset( $GLOBALS['drts_entity'] )
			&& $GLOBALS['drts_entity'] instanceof Entity\Type\IEntity
			&& $GLOBALS['drts_entity']->getBundleName() === $context->bundle->info['parent']
		) {
			$settings['push_state'] = false;
		} elseif ( $context->isEmbed() ) {
				// Let enable push_state if default view, otherwise disable.
				$settings['push_state'] = $this->Filter( 'view_entities_url_push_state', ! empty( $settings['is_default_view'] ), array( $context->bundle, ! empty( $settings['is_default_view'] ) ) );
		}
		return $settings;
	}

	protected function _filter( Context $context ) {
		$context->url_params_before_filter = $context->url_params;
		$render_filters                    = false;
		if ( isset( $context->settings['filter'] )
			&& $this->getComponent( 'View' )->isFilterable( $context->bundle )
		) {
			$render_filters = true;
			$this->_doFilter( $context );
			if ( ! empty( $context->settings['filter']['show'] ) ) {
				if ( ! empty( $context->is_default_view )
					&& ! empty( $context->settings['filter']['show_mobile_only'] )
					&& ! $this->isMobile()
				) {
					$context->settings['filter']['show'] = false;
				}
			}
			if ( ! $context->settings['filter']['show'] ) {
				if ( empty( $context->is_default_view )
					|| empty( $context->bundle->info['is_primary'] )
				) {
					$render_filters = false;
				}
			}
		}

		// Show filter form?
		if ( $render_filters ) {
			if ( ( $filter_form_settings = $this->_getFilterForm( $context ) )
				&& ( $filter_form = $this->Form_Form_build( $filter_form_settings, true, $this->_getRequestedFilters( $context ) ) )
			) {
				$context->filter['form'] = $filter_form;
			} else {
				$context->filter['form'] = null;
			}
		}
	}

	protected function _doFilter( Context $context ) {
		// Do filter?
		if ( ! $this->_isFilterRequested( $context )
			|| ( ! $request_params = $this->_getRequestedFilters( $context ) )
		) {
			return;
		}

		$filters_filterable = $filter_requests = $filters = $filter_values = $filter_labels = array();
		$display_name       = isset( $context->settings['filter']['display'] ) ? $context->settings['filter']['display'] : 'default';
		foreach ( $this->getModel( 'Filter', 'View' )->bundleName_is( $context->bundle->name )->displayName_is( $display_name )->fetch()->with( 'Field', 'FieldConfig' ) as $filter ) {
			$filters[ $filter->name ] = $filter;
			if ( isset( $request_params[ $filter->name ] )
				&& ( $ifilter = $this->Field_Filters_impl( $filter->type, true ) )
				&& ( $field = $filter->getField() )
				&& $ifilter->fieldFilterIsFilterable( $field, $filter->data['settings'], $request_params[ $filter->name ], $request_params )
			) {
				$filters_filterable[ $filter->type ][ $filter->name ] = $filter->name;
				$filter_requests[ $filter->name ]                     = $request_params[ $filter->name ];
			}
		}
		if ( ! empty( $filters_filterable ) ) {
			// Create and submit filter form
			$filter_form_settings    = $this->_getFilterForm(
				$context,
				array(
					'filters'            => $filters,
					'values'             => $filter_requests,
					'enable_facet_count' => false,
				)
			);
			$context->filter['form'] = $this->Form_Form_build( $filter_form_settings );
			if ( $context->filter['form']->submit( $filter_requests, true ) ) { // force submit since there is no form build ID
				$filter_values = $context->filter['form']->values;
				foreach ( array_keys( $filters_filterable ) as $filter_type ) {
					foreach ( $filters_filterable[ $filter_type ] as $filter_name ) {
						if ( ! isset( $filter_values[ $filter_name ] ) ) { // form validation failed
							unset( $filters_filterable[ $filter_type ][ $filter_name ], $filter_requests[ $filter_name ] );
						}
					}
				}
				if ( ! empty( $filter_requests ) ) {
					$context->url_params['filter'] = 1;
					$context->url_params          += $filter_requests;
				}
			} else {
				if ( $context->filter['form']->hasError() && $context->getRequest()->isAjax() ) {
					$errors = $context->filter['form']->getError();
					foreach ( array_keys( $errors ) as $key ) {
						$errors[ $key ] = ( $label = $context->filter['form']->getLabel( $key ) ) ? $label . ': ' . $errors[ $key ] : $errors[ $key ];
					}
					$context->setValidateFormError( null, implode( ' ', $errors ) );
					return;
				}
				$filters_filterable = array();
			}

			// Apply filters and add remove filter links
			foreach ( array_keys( $filters_filterable ) as $filter_type ) {
				$filter_impl = $this->Field_Filters_impl( $filter_type );
				foreach ( $filters_filterable[ $filter_type ] as $filter_name ) {
					$filter = $filters[ $filter_name ];
					if ( ! $field = $filter->getField() ) {
						continue;
					}

					if ( false === $filter_impl->fieldFilterDoFilter(
						$context->query->getFieldQuery(),
						$field,
						$filter->data['settings'],
						$filter_values[ $filter_name ],
						$context->sorts
					) ) {
						unset( $filters_filterable[ $filter_type ][ $filter_name ], $filter_requests[ $filter_name ] );
						continue;
					}

					$default_label = $field->getFieldLabel( true );
					if ( ! $_filter_labels = $filter_impl->fieldFilterLabels(
						$field,
						$filter->data['settings'],
						$filter_values[ $filter_name ],
						$filter_form_settings[ $filter_name ],
						$default_label
					) ) {
						$_filter_labels = array( '' => $default_label );
					}
					$filter_labels[ $filter_name ] = $_filter_labels;
				}
			}

			$context->filter['filters']                = $filters;
			$context->filter['filter_values']          = $filter_requests;
			$context->filter['filters_applied']        = $filters_filterable;
			$context->filter['filters_applied_labels'] = $filter_labels;
		}
	}

	protected function _isFilterFacetCountEnabled( Context $context ) {
		return empty( $context->settings['query']['limit'] ) && $this->getComponent( 'View' )->getConfig( 'filters', 'facet_count' );
	}

	protected function _isFilterRequested( Context $context ) {
		return $context->getRequest()->asBool( 'filter', false );
	}

	protected function _getRequestedFilters( Context $context ) {
		return $context->getRequest()->getParams();
	}

	protected function _getSorts( Context $context, array $settings ) {
		$possible_sorts = $this->_getAvailableSorts( $context->bundle );
		if ( ! empty( $settings['options'] ) ) {
			$ret = array();
			foreach ( $settings['options'] as $sort_name ) {
				if ( ! isset( $possible_sorts[ $sort_name ] ) ) {
					continue;
				}

				$ret[ $sort_name ] = $possible_sorts[ $sort_name ];
			}
		} else {
			$ret = $possible_sorts;
		}

		return $ret;
	}

	protected function _getAvailableSorts( Entity\Model\Bundle $bundle ) {
		return $this->Entity_Sorts( $bundle );
	}

	protected function _getUrlParams( Context $context ) {
		$ret = isset( $this->_settings['url_params'] ) ? (array) $this->_settings['url_params'] : array();
		if ( $this->_settingsCacheId ) {
			$ret['settings_cache_id'] = $this->_settingsCacheId;
		}
		return $ret;
	}

	/*
	 * @return Entity\Model\Bundle
	 */
	protected function _getBundle( Context $context ) {
		return $context->child_bundle ?: ( $context->taxonomy_bundle ?: $context->bundle );
	}

	protected function _getFilterForm( Context $context, array $settings = array() ) {
		if ( ! isset( $context->settings['filter']['target'] ) ) {
			$context->settings['filter']['target'] = $context->getContainer();
		}
		$settings += array(
			'filters'            => null,
			'current'            => null,
			'values'             => null,
			'enable_facet_count' => $this->_isFilterFacetCountEnabled( $context ),
		);
		// Create or rebuild form with query
		if ( isset( $context->filter['form'] ) ) {
			// Form already exists
			if ( empty( $context->filter['filters'] )
				|| ! $settings['enable_facet_count']
			) {
				return $context->filter['form']->settings;
			}
			// Regenerate with query to enable facets
			$settings['filters'] = $context->filter['filters'];
			$settings['current'] = $context->filter['form']->settings;
			$settings['values']  = $context->filter['filter_values'];
		}
		$options = $this->_getFilterFormOptions(
			$context,
			$context->url_params_before_filter,
			$settings
		);
		return $this->View_FilterForm(
			$context->bundle->name,
			$context->query,
			empty( $context->settings['filter']['display'] ) ? 'default' : $context->settings['filter']['display'],
			$options
		);
	}

	protected function _getFilterFormOptions( Context $context, array $urlParams = array(), array $options = array() ) {
		$url = $this->Url( $context->getRoute(), $urlParams );
		if ( isset( $context->entity )
			&& ( $context->entity->isTaxonomyTerm() || $context->entity->getBundleName() === $context->bundle->name )
		) {
			$url = $this->Entity_PermalinkUrl( $context->entity );
		}
		$options += array(
			'url'                => $url,
			'container'          => $context->settings['filter']['target'],
			'filters'            => null,
			'values'             => null,
			'push_state'         => ! empty( $this->_settings['push_state'] ),
			'current'            => null,
			'enable_facet_count' => false,
		);
		return $options;
	}
}
