<?php

namespace SilverStripe\ORM\FieldType;

use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Config\Config;
use SilverStripe\Dev\Deprecation;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DB;

/**
 * Represents a classname selector, which respects obsolete clasess.
 */
class DBClassName extends DBEnum
{

    /**
     * Base classname of class to enumerate.
     * If 'DataObject' then all classes are included.
     * If empty, then the baseClass of the parent object will be used
     *
     * @var string|null
     */
    protected $baseClass = null;

    /**
     * Parent object
     *
     * @var DataObject|null
     */
    protected $record = null;

    private static $index = true;

    /**
     * Clear all cached classname specs. It's necessary to clear all cached subclassed names
     * for any classes if a new class manifest is generated.
     */
    public static function clear_classname_cache()
    {
        Deprecation::notice('4.3', 'Call DBEnum::flushCache() instead');
        DBEnum::flushCache();
    }

    /**
     * Create a new DBClassName field
     *
     * @param string      $name      Name of field
     * @param string|null $baseClass Optional base class to limit selections
     * @param array       $options   Optional parameters for this DBField instance
     */
    public function __construct($name = null, $baseClass = null, $options = [])
    {
        $this->setBaseClass($baseClass);
        parent::__construct($name, null, null, $options);
    }

    /**
     * @return void
     */
    public function requireField()
    {
        $parts = [
            'datatype' => 'enum',
            'enums' => $this->getEnumObsolete(),
            'character set' => 'utf8',
            'collate' => 'utf8_general_ci',
            'default' => $this->getDefault(),
            'table' => $this->getTable(),
            'arrayValue' => $this->arrayValue
        ];

        $values = [
            'type' => 'enum',
            'parts' => $parts
        ];

        DB::require_field($this->getTable(), $this->getName(), $values);
    }

    /**
     * Get the base dataclass for the list of subclasses
     *
     * @return string
     */
    public function getBaseClass()
    {
        // Use explicit base class
        if ($this->baseClass) {
            return $this->baseClass;
        }
        // Default to the basename of the record
        $schema = DataObject::getSchema();
        if ($this->record) {
            return $schema->baseDataClass($this->record);
        }
        // During dev/build only the table is assigned
        $tableClass = $schema->tableClass($this->getTable());
        if ($tableClass && ($baseClass = $schema->baseDataClass($tableClass))) {
            return $baseClass;
        }
        // Fallback to global default
        return DataObject::class;
    }

    /**
     * Get the base name of the current class
     * Useful as a non-fully qualified CSS Class name in templates.
     *
     * @return string|null
     */
    public function getShortName()
    {
        $value = $this->getValue();
        if (empty($value) || !ClassInfo::exists($value)) {
            return null;
        }
        return ClassInfo::shortName($value);
    }

    /**
     * Assign the base class
     *
     * @param string $baseClass
     * @return $this
     */
    public function setBaseClass($baseClass)
    {
        $this->baseClass = $baseClass;
        return $this;
    }

    /**
     * Get list of classnames that should be selectable
     *
     * @return array
     */
    public function getEnum()
    {
        $classNames = ClassInfo::subclassesFor($this->getBaseClass());
        $dataobject = strtolower(DataObject::class);
        unset($classNames[$dataobject]);
        return array_values($classNames);
    }

    public function setValue($value, $record = null, $markChanged = true)
    {
        parent::setValue($value, $record, $markChanged);

        if ($record instanceof DataObject) {
            $this->record = $record;
        }
    }

    public function getDefault()
    {
        // Check for assigned default
        $default = parent::getDefault();
        if ($default) {
            return $default;
        }

        // Allow classes to set default class
        $baseClass = $this->getBaseClass();
        $defaultClass = Config::inst()->get($baseClass, 'default_classname');
        if ($defaultClass &&  class_exists($defaultClass)) {
            return $defaultClass;
        }

        // Fallback to first option
        $enum = $this->getEnum();
        return reset($enum);
    }
}
