<?php namespace ProcessWire;

/**
 * ProcessWire FieldtypeFieldsetOpen and InputfieldFieldsetOpen
 *
 * Provides a Fieldtype and Inputfield for opening a Fieldset. 
 *
 * For documentation about the fields used in this class, please see:  
 * /wire/core/Fieldtype.php
 * 
 * ProcessWire 3.x, Copyright 2016 by Ryan Cramer
 * https://processwire.com
 *
 *
 */

class InputfieldFieldsetOpen extends InputfieldWrapper { } 

class FieldtypeFieldsetOpen extends Fieldtype {

	/**
	 * Appended to the name of a 'Close' version of a FieldsetOpen
	 *
	 */
	const fieldsetCloseIdentifier = '_END';

	public static function getModuleInfo() {
		return array(
			'title' => 'Fieldset (Open)',
			'version' => 101,
			'summary' => 'Open a fieldset to group fields. Should be followed by a Fieldset (Close) after one or more fields.',
			'permanent' => true, 
			);
	}

	/**
	 * Have the hooks been setup for this Fieldset?
	 *
	 */
	protected static $hooked = false;

	/**
	 * Setup hooks to minotor when Fields are added and deleted
	 *
	 * But only do it once so that we don't have the same hooks setup when there are multiple Fieldsets
	 *
	 */
	public function init() {
		if(!self::$hooked && ($this->process == 'ProcessField' || $this->process = 'ProcessTemplate')) {
			$this->addProcessHooks();
		}
		self::$hooked = true;
	}

	/**
	 * Add hooks to ProcessField or ProcessTemplate, called only when applicable
	 * 
	 */
	protected function addProcessHooks() {
		$this->addHook('ProcessField::fieldAdded', $this, 'hookFieldAdded');
		$this->addHook('ProcessField::fieldDeleted', $this, 'hookFieldDeleted');
		$this->addHook('ProcessField::allowFieldInTemplate', $this, 'hookAllowFieldInTemplate');
		$this->addHook('ProcessTemplate::fieldAdded', $this, 'hookTemplateFieldAdded');
		$this->addHook('ProcessTemplate::fieldRemoved', $this, 'hookTemplateFieldRemoved'); 
	}

	public function sanitizeValue(Page $page, Field $field, $value) {
		return null;
	}

	public function getInputfield(Page $page, Field $field) {
		$inputfield = $this->wire(new InputfieldFieldsetOpen());
		$inputfield->class = $this->className();
		return $inputfield; 
	}

	public function savePageField(Page $page, Field $field) {
		return true; 
	}

	public function ___getConfigInputfields(Field $field) {
		$inputfields = parent::___getConfigInputfields($field);
		return $inputfields; 
	}

	public function ___getConfigAdvancedInputfields(Field $field) {
		$inputfields = parent::___getConfigAdvancedInputfields($field); 
		// these really aren't applicable with fieldsets, so remove them
		$inputfields->remove($inputfields->get('autojoin')); 
		$inputfields->remove($inputfields->get('global')); 
		return $inputfields; 
	}

	public function ___getCompatibleFieldtypes(Field $field) {
		$fieldtypes = $this->wire(new Fieldtypes());
		foreach($this->wire('fieldtypes') as $fieldtype) {
			if($fieldtype instanceof FieldtypeFieldsetOpen) $fieldtypes->add($fieldtype); 
		}
		return $fieldtypes; 
	}

	public function loadPageField(Page $page, Field $field) {
		return '';
	}

	public function getLoadQuery(Field $field, DatabaseQuerySelect $query) {
		return $query;
	}
	
	/**
	 * Is given field a fieldset opener?
	 * 
	 * For hooks to share in determining if this is a field they want to operate on
	 * 
	 * @param Field $field
	 * @return bool
	 *
	 */
	protected function isFieldset(Field $field) {
		return $field->type instanceof FieldtypeFieldsetOpen && !($field->type instanceof FieldtypeFieldsetClose);
	}

	/**
	 * Get the Field that closes/terminates the given Fieldset field
	 * 
	 * @param Field $field
	 * @param bool $createIfNotExists Create it if it doesn't already exist?
	 * @return null|Field of type FieldtypeFieldsetClose
	 * 
	 */
	public function getFieldsetCloseField(Field $field, $createIfNotExists = false) {
		
		if(!$this->isFieldset($field)) return;
	
		$name = $field->name . self::fieldsetCloseIdentifier;
		$closer = $this->wire('fields')->get($name); 
		
		if(!$closer) {
			$closeFieldID = (int) $field->get('closeFieldID');
			if($closeFieldID) {
				$closer = $this->wire('fields')->get($closeFieldID); 
			}
		}
		
		if(!$closer && $createIfNotExists) {
			$closer = $this->wire(new Field());
			$closer->type = $this->wire(new FieldtypeFieldsetClose());
			$closer->name = $name;
			$closer->label = "Close an open fieldset";
			$closer->description = 
				"This field was added automatically to accompany fieldset '$field'.  " . 
				"It should be placed in your template/fieldgroup wherever you want the fieldset to end.";
			$closer->set('openFieldID', $field->id); 
			$closer->save();
		} 
		
		if($closer && !$field->get('closeFieldID')) {
			$field->set('closeFieldID', $closer->id); 
			$field->save();
		}
		
		return $closer; 
	}

	/**
	 * Hook executed when field is added via ProcessField
	 * 
	 * @param HookEvent $event
	 *
	 */
	public function hookFieldAdded($event) {
		$field = $event->arguments[0]; 
		if(!$this->isFieldset($field)) return; 
		$closer = $this->getFieldsetCloseField($field, true); 
		$this->message("Also added field '$closer', to accompany '$field'"); 
	}

	/**
	 * Hook executed when field is deleted via ProcessField
	 * 
	 * @param HookEvent $event
	 *
	 */
	public function hookFieldDeleted($event) {

		$field = $event->arguments[0]; 
		if(!$this->isFieldset($field)) return; 

		$closer = $this->getFieldsetCloseField($field); 
		
		if($closer) {
			$this->wire('fields')->delete($closer);
			$this->message("Delete also issued for field '$closer'");
		}
	}

	/**
	 * Hook executed when field is added to a template via ProcessTemplate
	 * 
	 * @param HookEvent $event
	 *
	 */
	public function hookTemplateFieldAdded($event) {
		/** @var Field $field */
		/** @var Template $template */
		list($field, $template) = $event->arguments; 
		if(!$this->isFieldset($field)) return; 
		$closer = $this->getFieldsetCloseField($field); 
		if(!$closer) return; 
		if(!$template->fieldgroup->hasField($closer)) {
			$template->fieldgroup->add($closer);
			$this->message("Also added field '$closer', which should be placed where you want to close fieldset '$field'");
		}
	}

	/**
	 * Hook executed when field is removed from a template via ProcessTemplate
	 * 
	 * @param HookEvent $event
	 *
	 */
	public function hookTemplateFieldRemoved($event) {
		list($field, $template) = $event->arguments; 
		if(!$this->isFieldset($field)) return; 
		$closer = $this->getFieldsetCloseField($field); 
		if(!$closer) return; 
		$template->fieldgroup->remove($closer); 
		$this->message("Also removed '$closer', which accompanies '$field'"); 
	}

	/**
	 * Hook to ProcessField::allowFieldInTemplate
	 * 
	 * @param HookEvent $event
	 * 
	 */
	public function hookAllowFieldInTemplate(HookEvent $event) { 
		/** @var Field $field */
		// $field = $event->arguments(0);	
		/** @var Template $template */
		// $template = $event->arguments(1);
	}

	/**
	 * Hook called by Fields::save() after a field using this type has been renamed
	 *
	 * Note that PW already takes care of renaming the field_[name] table.
	 * Most Fieldtypes don't need to do anything here, but this exists just in case.
	 *
	 * #pw-internal
	 *
	 * @param Field $field
	 * @param string $prevName Previous name (current name can be found in $field->name)
	 *
	 */
	public function ___renamedField(Field $field, $prevName) {
		// avoid ending up in infinite loop, since FieldtypeFieldsetClose extends this
		if($this instanceof FieldtypeFieldsetClose) return;
		// rename the _END field to match this one
		$fields = $this->wire('fields');
		$closer = $this->getFieldsetCloseField($field); 
		if(!$closer) {
			$closer = $fields->get($prevName . self::fieldsetCloseIdentifier);
			if($closer) {
				$closer->name = $field->name . self::fieldsetCloseIdentifier;
				$fields->save($closer);
			}
		}
		parent::___renamedField($field, $prevName); 
	}
}


