<?php namespace ProcessWire;

/**
 * ProcessWire Decimal Fieldtype
 *
 * Field that stores a decimal number.
 *
 * For documentation about the fields used in this class, please see:
 * /wire/core/Fieldtype.php
 *
 * ProcessWire 3.x, Copyright 2020 by Ryan Cramer
 * https://processwire.com
 *
 */

class FieldtypeDecimal extends Fieldtype {

	public static function getModuleInfo() {
		return array(
			'title' => __('Decimal', __FILE__),
			'summary' => __('Field that stores a decimal number', __FILE__),
			'version' => 1,
		);
	}

	/**
	 * Get blank value
	 *
	 * @param Page $page
	 * @param Field $field
	 * @return string
	 *
	 */
	public function getBlankValue(Page $page, Field $field) {
		return '';
	}

	/**
	 * Is given value considered empty to this Fieldtype?
	 *
	 * @param Field $field
	 * @param mixed $value
	 * @return bool
	 *
	 */
	public function isEmptyValue(Field $field, $value) {
		// when zeroNotEmpty option is set, we don't count a literal "0" is being a blank value
		if($field->get('zeroNotEmpty') && strlen("$value") && !strlen(trim($value, "0.,"))) return false;
		return empty($value);
	}

	/**
	 * Sanitize value
	 *
	 * @param Page $page
	 * @param Field $field
	 * @param float|int|string $value
	 * @return string
	 *
	 */
	public function sanitizeValue(Page $page, Field $field, $value) {
		
		list(/*$digits*/, $precision) = $this->getDigitsAndPrecision($field); 
	
		if(!strlen("$value")) return '';
		
		$value = $this->wire()->sanitizer->float($value, array(
			'precision' => null,
			'blankValue' => '',
			'getString' => true,
		));
		
		if(!strlen($value)) return '';
		
		if(strpos($value, '.') !== false) {
			list($a, $b) = explode('.', $value, 2);
			if(!strlen($a)) $a = '0';
			while(strlen($b) < $precision) $b .= '0';
			$value = "$a.$b";
			if(strlen($b) > $precision) {
				$value = (string) round((float) $value, $precision);
			}
		}
	
		return $value;
	}

	/**
	 * Get Inputfield for this Fieldtype
	 *
	 * @param Page $page
	 * @param Field $field
	 * @return Inputfield
	 *
	 */
	public function getInputfield(Page $page, Field $field) {
		/** @var InputfieldFloat $inputfield */
		$inputfield = $this->wire()->modules->get('InputfieldFloat');
		$inputfield->set('precision', $field->get('precision'));
		return $inputfield;
	}

	/**
	 * Sleep value for DB storage
	 *
	 * @param Page $page
	 * @param Field $field
	 * @param string|float|int
	 * @return string
	 *
	 */
	public function ___sleepValue(Page $page, Field $field, $value) {
		$value = (string) $value;
		if(!strlen($value)) return $value;
		if(!ctype_digit(str_replace('.', '', $value))) $value = $this->sanitizeValue($page, $field, $value);
		return $value;
	}
	
	/**
	 * Get compatible Fieldtypes
	 *
	 * @param Field $field
	 * @return null|Fieldtypes
	 *
	 */
	public function ___getCompatibleFieldtypes(Field $field) {
		$fieldtypes = parent::___getCompatibleFieldtypes($field);
		foreach($fieldtypes as $type) {
			if($type instanceof FieldtypeDecimal) continue;
			if($type instanceof FieldtypeInteger || $type instanceof FieldtypeFloat) continue;
			$fieldtypes->remove($type);
		}
		return $fieldtypes;
	}

	/**
	 * Get DB schema for this Fieldtype
	 *
	 * @param Field $field
	 * @return array
	 *
	 */
	public function getDatabaseSchema(Field $field) {
		$schema = parent::getDatabaseSchema($field);
		list($digits, $precision) = $this->getDigitsAndPrecision($field); 
		$schema['data'] = "DECIMAL($digits,$precision)";
		return $schema;
	}
	
	/**
	 * Called when Field using this Fieldtype has been saved
	 * 
	 * Check and update database schema as necessary
	 *
	 * @param Field $field
	 *
	 */
	public function ___savedField(Field $field) {
		$database = $this->wire()->database;
		$info = $database->columnExists($field->getTable(), 'data', true);
		if(!$info) return;
		$table = $field->getTable();
		$schema = $this->getDatabaseSchema($field);
		$colType = str_replace(' ', '', strtoupper($info['Type']));
		$defType = str_replace(' ', '', strtoupper($schema['data']));
		if($colType !== $defType) {
			$database->exec("ALTER TABLE `$table` MODIFY `data` $defType");
			$this->message(sprintf($this->_('Updated field “%1$s” schema: %2$s => %3$s'), $field->name, $colType, $defType)); 
		}
	}

	/**
	 * Get digits and precision 
	 * 
	 * @param Field $field
	 * @return array of [ digits, precision ]
	 * 
	 */
	protected function getDigitsAndPrecision(Field $field) {
		
		$digits = $field->get('digits');
		if($digits === null || $digits < 1) $digits = 10;
		$digits = (int) $digits;
		
		$precision = $field->get('precision');
		if($precision === null || $precision < 0) $precision = 2;
		$precision = (int) $precision;
		
		if($digits < $precision) $digits = $precision;
		if($digits > 65) $digits = 65;
		
		return array($digits, $precision);
	}
	
	/**
	 * Get field configuration
	 *
	 * @param Field $field
	 * @return InputfieldWrapper
	 *
	 */
	public function ___getConfigInputfields(Field $field) {
		
		$inputfields = parent::___getConfigInputfields($field);
		list($digits, $precision) = $this->getDigitsAndPrecision($field); 
	
		$example = $this->_('For example:') . ' ';
		
		/** @var InputfieldInteger $f */
		$f = $this->wire()->modules->get('InputfieldInteger');
		$f->attr('name', 'digits');
		$f->label = $this->_('Total number of supported digits');
		$f->description = $this->_('Includes digits both before and after the decimal point.'); 
		$f->val($digits);
		$f->inputType = 'number';
		$f->columnWidth = 50;
		$f->min = 1;
		$f->max = 65;
		$f->detail = $example . $this->_('999.99 is 5 digits total so would require a value of 5 or higher.');
		$inputfields->add($f);

		/** @var InputfieldInteger $f */
		$f = $this->wire()->modules->get('InputfieldInteger');
		$f->attr('name', 'precision');
		$f->label = $this->_('Number of digits present after decimal');
		$f->description = $this->_('This value will be less than (or in some cases equal to) the total number of digits.'); 
		$f->detail = $example . $this->_('999.99 and 100.00 have 2 digits after the decimal');
		$f->val((int) $precision);
		$f->inputType = 'number';
		$f->columnWidth = 50;
		$f->min = 0;
		$f->max = 65;
		$inputfields->add($f);

		/** @var FieldtypeInteger $ft */
		$ft = $this->wire()->fieldtypes->FieldtypeInteger;
		
		// use the same 'zeroNotEmpty' setting as FieldtypeInteger
		$f = $ft->___getConfigInputfields($field)->getChildByName('zeroNotEmpty');
		if($f) $inputfields->add($f);

		return $inputfields;
	}
}

