<?php namespace ProcessWire;

/**
 * ProcessWire Module Fieldtype
 *
 * Field that stores reference to another Module. 
 *
 * 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 FieldtypeModule extends Fieldtype {

	public static function getModuleInfo() {
		return array(
			'title' => 'Module Reference',
			'version' => 101,
			'summary' => 'Field that stores a reference to another module',
			'permanent' => true, 
			);
	}

	public function getBlankValue(Page $page, Field $field) {
		$blankType = $field->get('blankType');
		if($blankType === 'zero') {
			$value = 0;
		} else if($blankType === 'false') {
			$value = false;
		} else if($blankType === 'placeholder') {
			$value = $this->wire(new ModulePlaceholder());
		} else {
			$value = null;
		}
		return $value;
	}

	public function isAdvanced() {
		return true; 
	}

	public function sanitizeValue(Page $page, Field $field, $value) {
		if(!$value) return $this->getBlankValue($page, $field);
		if($field->get('instantiateModule')) return $value instanceof Module ? $value : $this->modules->get($value); 
		if(ctype_digit("$value")) return $this->modules->getModuleClass((int) $value); 
		return $this->modules->getModuleID($value) ? $value : $this->getBlankValue($page, $field);
	}

	public function ___wakeupValue(Page $page, Field $field, $value) {
		if(empty($value)) return $this->getBlankValue($page, $field);
		if($field->get('instantiateModule')) return $this->wire('modules')->get($value); 
		return $this->wire('modules')->getModuleClass((int) $value); 
	}

	public function ___sleepValue(Page $page, Field $field, $value) {
		$blankValue = $this->getBlankValue($page, $field);
		if(!$value || "$blankValue" == "$value") {
			return 0;
		} else {
			return $this->modules->getModuleID($value);
		}
	}

	public function getInputfield(Page $page, Field $field) {
	
		/** @var Modules $modules */
		$modules = $this->wire('modules');

		$inputfieldClass = $field->get('inputfieldClass');
		$inputfieldClass = $inputfieldClass ? $inputfieldClass : 'InputfieldSelect';
		
		/** @var InputfieldSelect $inputfield */
		$inputfield = $modules->get($inputfieldClass); 
		if(!$inputfield) $inputfield = $this->modules->get('InputfieldSelect'); 

		$inputfield->attr('name', $field->name); 
		$inputfield->class = $this->className();
		$options = array();

		foreach($modules as $module) {
			$found = false; 
			$ns = $this->modules->getModuleNamespace($module);
			$parents = wireClassParents($ns ? "$ns$module" : "$module"); 
			$moduleTypes = $field->get('moduleTypes');
			if($moduleTypes) foreach($moduleTypes as $moduleType) {
				if(in_array($moduleType, $parents)) { 
					$found = true; 
					break;
				}
			}
			if(!$found) continue; 
			$labelField = $field->get('labelField');
			if($labelField === 'title') {
				$info = $modules->getModuleInfo($module);
				$label = !empty($info['title']) ? $info['title'] : (string) $module;
			} else if($labelField === 'title-summary') {
				$info = $modules->getModuleInfoVerbose($module);
				$label = !empty($info['title']) ? $info['title'] : (string) $module;
				if(!empty($info['summary'])) $label .= " [span.detail] • " . $info['summary'] . ' [/span]';
			} else {
				$label = (string) $module; 
			}

			$options[$label] = $module; 
		}

		ksort($options); 
		
		if($inputfieldClass == 'InputfieldRadios' && $field->get('showNoneOption')) {
			$inputfield->addOption(0, $this->_('None'));
		}

		foreach($options as $label => $module) {
			$inputfield->addOption((string) $module, $label); 
		}

		return $inputfield; 
	}

	public function getDatabaseSchema(Field $field) {
		$schema = parent::getDatabaseSchema($field); 
		$schema['data'] = 'int NOT NULL';
		return $schema;
	}

	public function ___getConfigInputfields(Field $field) {

		$inputfields = parent::___getConfigInputfields($field); 
		$lastType = '';

		/** @var InputfieldCheckboxes $f */
		$f = $this->modules->get("InputfieldCheckboxes"); 
		$f->attr('name', 'moduleTypes'); 
		
		foreach($this->modules as $module) {
			if(strpos($module->className(), 'AdminTheme') === 0) {
				$matches = array('', 'AdminTheme');
			} else {
				if(!preg_match('/^([A-Za-z][a-z0-9_]+)/', $module->className(), $matches)) continue;
			}
			$moduleType = $matches[1];
			if($moduleType == $lastType) continue; 	
			$f->addOption($moduleType); 
			$lastType = $moduleType; 
		}

		$moduleTypes = $field->get('moduleTypes');
		if(!is_array($moduleTypes)) $moduleTypes = array();
		
		$f->attr('value', $moduleTypes);
		$f->label = $this->_('Module Types');
		$f->description = $this->_('Check all of the module types that may be selectable in this field.');
		$inputfields->append($f); 

		/** @var InputfieldCheckbox $f */
		$f = $this->modules->get("InputfieldCheckbox"); 
		$f->attr('name', 'instantiateModule'); 
		$f->label = $this->_('Make this field an instance of the selected module?'); 
		$f->description = $this->_('If checked, the field value will be an actual instance of the selected module. If not checked, the field value will be a string containing the class name of the module.'); // instantiate module description
		if($field->get('instantiateModule')) $f->attr('checked', 'checked'); 
		$inputfields->add($f); 

		/** @var InputfieldRadios $f */
		$f = $this->modules->get('InputfieldRadios'); 
		$f->label = $this->_('Options Label'); 
		$f->attr('name', 'labelField'); 
		$f->addOption('', $this->_('Name')); 
		$f->addOption('title', $this->_('Title'));
		$f->addOption('title-summary', $this->_('Title and summary'));
		$f->attr('value', $field->get('labelField')); 
		$f->columnWidth = 50; 
		$inputfields->add($f);

		/** @var InputfieldRadios $f */
		$f = $this->modules->get('InputfieldRadios'); 
		$f->label = $this->_('Input Type'); 
		$f->attr('name', 'inputfieldClass'); 
		$f->addOption('', $this->_('Select')); 
		$f->addOption('InputfieldRadios', $this->_('Radios')); 
		$f->columnWidth = 50;
		$f->attr('value', $field->get('inputfieldClass'));
		$inputfields->add($f); 
	
		/** @var InputfieldCheckbox $f */
		$f = $this->modules->get('InputfieldCheckbox');
		$f->label = $this->_('Show a “None” option?');
		$f->attr('name', 'showNoneOption');
		if($field->get('showNoneOption')) $f->attr('checked', 'checked'); 
		$f->showIf = 'inputfieldClass=InputfieldRadios';
		$inputfields->add($f);
	
		/** @var InputfieldRadios $f */
		$f = $this->modules->get('InputfieldRadios');
		$f->attr('name', 'blankType');
		$f->label = $this->_('Blank value type');
		$f->addOption('null', 'Null');
		$f->addOption('zero', 'Integer 0');
		$f->addOption('false', 'Boolean false');
		$f->addOption('placeholder', 'ModulePlaceholder instance');
		$f->attr('value', $field->get('blankType')); 
		$inputfields->add($f);

		return $inputfields; 			
	}

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

