<?php

namespace LibreNMS;

abstract class Model
{
    protected static $table;
    protected static $primaryKey = 'id';

    public static function create(array $data)
    {
        $instance = new static();
        $instance->fill($data);
        return $instance;
    }

    protected function fill(array $data = array())
    {
        foreach ($data as $field => $value) {
            $this->$field = $value;
        }
    }

    /**
     * Save Models and remove invalid Models
     * This the sensors array should contain all the sensors of a specific class
     * It may contain sensors from multiple tables and devices, but that isn't the primary use
     *
     * @param int $device_id
     * @param array $models
     * @param array $unique_fields fields to search for an existing entry
     * @param array $ignored_update_fields Don't compare these field when updating
     */
    final public static function sync($device_id, array $models, $unique_fields = array(), $ignored_update_fields = array())
    {
        // save and collect valid ids
        $valid_ids = array();
        foreach ($models as $model) {
            /** @var $this $model */
            if ($model->isValid()) {
                $valid_ids[] = $model->save($unique_fields, $ignored_update_fields);
            }
        }

        // delete invalid sensors
        self::clean($device_id, $valid_ids);
    }

    /**
     * Remove invalid Models.  Passing an empty array will remove all models related to $device_id
     *
     * @param int $device_id
     * @param array $model_ids valid Model ids
     */
    protected static function clean($device_id, $model_ids)
    {
        $table = static::$table;
        $key = static::$primaryKey;

        $params = array($device_id);
        $where = '`device_id`=?';

        if (!empty($model_ids)) {
            $where .= " AND `$key` NOT IN " . dbGenPlaceholders(count($model_ids));
            $params = array_merge($params, $model_ids);
        }

        $rows = dbFetchRows("SELECT * FROM `$table` WHERE $where", $params);
        foreach ($rows as $row) {
            static::onDelete(static::create($row));
        }
        if (!empty($rows)) {
            dbDelete($table, $where, $params);
        }
    }

    /**
     * Save this Model to the database.
     *
     * @param array $unique_fields fields to search for an existing entry
     * @param array $ignored_update_fields Don't compare these field when updating
     * @return int the id of this model in the database
     */
    final public function save($unique_fields = array(), $ignored_update_fields = array())
    {
        $db_model = $this->fetch($unique_fields);
        $key = static::$primaryKey;

        if ($db_model) {
            $new_model = $this->toArray(array_merge(array($key), $ignored_update_fields));
            $update = array_diff($new_model, $db_model);

            if (empty($update)) {
                static::onNoUpdate();
            } else {
                dbUpdate($update, static::$table, "`$key`=?", array($this->$key));
                static::onUpdate($this);
            }
        } else {
            $new_model = $this->toArray(array($key));
            $this->$key = dbInsert($new_model, static::$table);
            if ($this->$key !== null) {
                static::onCreate($this);
            }
        }

        return $this->$key;
    }

    /**
     * Fetch the sensor from the database.
     * If it doesn't exist, returns null.
     *
     * @param array $unique_fields fields to search for an existing entry
     * @return array|null
     */
    protected function fetch($unique_fields = array())
    {
        $table = static::$table;
        $key = static::$primaryKey;

        if (isset($this->id)) {
            return dbFetchRow(
                "SELECT `$table` FROM ? WHERE `$key`=?",
                array($this->$key)
            );
        }

        $where = array();
        $params = array();
        foreach ($unique_fields as $field) {
            if (isset($this->$field)) {
                $where[] = " $field=?";
                $params[] = $this->$field;
            }
        }

        if (empty($params)) {
            return null;
        }

        $row = dbFetchRow(
            "SELECT * FROM `$table` WHERE " . implode(' AND', $where),
            $params
        );

        $this->$key = $row[$key];
        return $row;
    }

    /**
     * Convert this Model to an array with fields that match the database
     *
     * @param array $exclude Exclude the listed fields
     * @return array
     */
    abstract public function toArray($exclude = array());

    /**
     * Returns if this model passes validation and should be saved to the database
     *
     * @return bool
     */
    abstract public function isValid();

    /**
     * @param static $model
     */
    public static function onDelete($model)
    {
        if (isCli()) {
            echo '-';
        }
    }

    /**
     * @param static $model
     */
    public static function onCreate($model)
    {
        if (isCli()) {
            echo '+';
        }
    }

    /**
     * @param static $model
     */
    public static function onUpdate($model)
    {
        if (isCli()) {
            echo 'U';
        }
    }

    public static function onNoUpdate()
    {
        if (isCli()) {
            echo '.';
        }
    }
}
