<?php

/* --------------------------------------------------------------------

  This file is part of Chevereto Free.
  https://chevereto.com/free

  (c) Rodolfo Berrios <rodolfo@chevereto.com>

  For the full copyright and license information, please view the LICENSE
  file that was distributed with this source code.

  --------------------------------------------------------------------- */

namespace CHV;

use G;
use Exception;
use SEOURLify;

class Album
{
    public static function getSingle($id, $sumview = false, $pretty = true, $requester = null)
    {
        $tables = DB::getTables();
        $query = 'SELECT * FROM ' . $tables['albums'] . "\n";
        $joins = [
            'LEFT JOIN ' . $tables['users'] . ' ON ' . $tables['albums'] . '.album_user_id = ' . $tables['users'] . '.user_id'
        ];

        if ($requester) {
            if (!is_array($requester)) {
                $requester = User::getSingle($requester, 'id');
            }

        }

        $query .=  implode("\n", $joins) . "\n";
        $query .= 'WHERE album_id=:album_id;' . "\n";

        if ($sumview) {
            $query .= 'UPDATE ' . $tables['albums'] . ' SET album_views = album_views + 1 WHERE album_id=:album_id';
        }

        try {
            $db = DB::getInstance();
            $db->query($query);
            $db->bind(':album_id', $id);
            $album_db = $db->fetchSingle();
            if (!$album_db) {
                return $album_db;
            }

            if ($sumview) {
                $album_db['album_views'] += 1;
                // Track stats
                Stat::track([
                    'action'    => 'update',
                    'table'        => 'albums',
                    'value'        => '+1',
                    'user_id'    => $album_db['album_user_id'],
                ]);
            }

            if ($requester) {
                $album_db['album_liked'] = (bool) $album_db['like_user_id'];
            }
            $return = $album_db;
            $return = $pretty ? self::formatArray($return) : $return;
            return $return;
        } catch (Exception $e) {
            throw new AlbumException($e->getMessage(), 400);
        }
    }

    public static function getMultiple($ids, $pretty = false)
    {
        if (!is_array($ids)) {
            throw new AlbumException('Expecting $ids array in ' . __METHOD__, 100);
        }
        if (count($ids) == 0) {
            throw new AlbumException('Null $ids provided in ' . __METHOD__, 100);
        }

        $tables = DB::getTables();
        $query = 'SELECT * FROM ' . $tables['albums'] . "\n";
        $joins = array(
            'LEFT JOIN ' . $tables['users'] . ' ON ' . $tables['albums'] . '.album_user_id = ' . $tables['users'] . '.user_id'
        );

        $query .=  implode("\n", $joins) . "\n";
        $query .= 'WHERE album_id IN (' . join(',', $ids) . ')' . "\n";

        try {
            $db = DB::getInstance();
            $db->query($query);
            $db_rows = $db->fetchAll();
            if ($pretty) {
                $return = [];
                foreach ($db_rows as $k => $v) {
                    $return[$k] = self::formatArray($v);
                }
                return $return;
            }
            return $db_rows;
        } catch (Exception $e) {
            throw new AlbumException($e->getMessage(), 400);
        }
    }

    public static function sumView($id, $album = [])
    {
        try {
            if (!G\is_integer($id)) {
                throw new Exception('Invalid $id in ' . __METHOD__);
            }
            if ($album['id'] !== $id) {
                $album = self::getSingle($id, false);
                if (!$album) {
                    throw new Exception(sprintf('Invalid album %s in ' . __METHOD__, $id));
                }
            }
            $increment = '+1';
            DB::increment('albums', ['views' => $increment], ['id' => $id]);
            Stat::track([
                'action'    => 'update',
                'table'        => 'albums',
                'value'        => $increment,
                'user_id'    => $album['album_user_id'],
            ]);
            $_SESSION['album_view_stock'][] = $id;
        } catch (Exception $e) {
            throw new AlbumException($e->getMessage(), 400);
        }
    }

    public static function getUrl($album_id, $title = null)
    {
        if ($title == null) {
            $seo = null;
        } else {
            $seo = SEOURLify::filter($title);
        }
        $url = $seo ? ($seo . '.' . $album_id) : $album_id;
        return G\get_base_url(getSetting('route_album') . '/' . $url);
    }

    // $name, $user_id = null, $privacy = 'public', $description = '', $password = null
    public static function insert($values)
    {
        if (!$values['user_id']) {
            $values['user_id'] = null;
        }
        if (!$values['description']) {
            $values['description'] = '';
        }
        if (!$values['password']) {
            $values['password'] = null;
        }
        if ($values['privacy'] == 'password' && !G\check_value($values['password'])) {
            throw new AlbumException('Missing album $password', 101);
        }
        // Handle flood
        $flood = self::handleFlood();
        if ($flood) {
            throw new AlbumException(
                _s(
                    'Flooding detected. You can only upload %limit% %content% per %time%',
                    [
                        '%content%' => _n('album', 'albums', $flood['limit']),
                        '%limit%' => $flood['limit'],
                        '%time%' => $flood['by']
                    ]
                ),
                130
            );
        }
        if (!$values['name']) {
            $values['name'] = _s('Untitled') . ' ' . G\datetime();
        }
        $privacyOpts = ['public', 'password', 'private_but_link'];
        if (Login::isLoggedUser()) {
            $privacyOpts[] = 'private';
        }
        if (in_array($values['privacy'], $privacyOpts) == false) {
            $values['privacy'] = 'public';
        }
        G\nullify_string($values['description']);
        if (empty($values['creation_ip'])) {
            $values['creation_ip'] = G\get_client_ip();
        }
        $album_array = [
            'name'            => $values['name'],
            'user_id'        => $values['user_id'],
            'date'            => G\datetime(),
            'date_gmt'        => G\datetimegmt(),
            'privacy'        => $values['privacy'],
            'password'        => $values['privacy'] == 'password' ? $values['password'] : null,
            'description'    => $values['description'],
            'creation_ip'    => $values['creation_ip']
        ];
        try {
            $insert = DB::insert('albums', $album_array);
            // +1 on user
            if (Login::isLoggedUser()) {
                DB::increment('users', ['album_count' => '+1'], ['id' => $values['user_id']]);
            } else {
                // Save this upload into "session" record
                if (!isset($_SESSION['guest_albums'])) {
                    $_SESSION['guest_albums'] = [];
                }
                $_SESSION['guest_albums'][] = $insert;
            }
            // Track stats
            Stat::track([
                'action'    => 'insert',
                'table'        => 'albums',
                'value'        => '+1',
                'date_gmt'    => $album_array['date_gmt']
            ]);
            return $insert;
        } catch (Exception $e) {
            throw new AlbumException($e->getMessage(), 400);
        }
    }

    // Move contents $from albums to another album
    public static function moveContents($from, $to)
    {
        if (!$from) { // Could be int or array (multiple)
            throw new AlbumException('Expecting first parameter, ' . gettype($from) . ' given in ' . __METHOD__, 100);
        }

        if (!$to) {
            $to = null;
        }

        $ids = is_array($from) ? $from : array($from);

        try {
            $db = DB::getInstance();
            $db->query('UPDATE ' . DB::getTable('images') . ' SET image_album_id=:image_album_id WHERE image_album_id IN (' . implode(',', $ids) . ')');
            $db->bind(':image_album_id', $to);
            $images = $db->exec();
            if ($images) {
                $images_affected = $db->rowCount();
                // Update the old and new albums to +ids
                $db->query(
                    'UPDATE ' . DB::getTable('albums') . ' SET album_image_count = 0 WHERE album_id IN (' . implode(',', $ids) . ');' .
                        'UPDATE ' . DB::getTable('albums') . ' SET album_image_count = album_image_count + ' . $images_affected . ' WHERE album_id=:album_id;'
                );
                $db->bind(':album_id', $to);
                $db->exec();
            } else {
                return false;
            }
            return true;
        } catch (Exception $e) {
            throw new AlbumException($e->getMessage(), 400);
        }
    }

    public static function addImage($album_id, $id)
    {
        return self::addImages($album_id, array($id));
    }

    public static function addImages($album_id, $ids)
    {

        // $album_id can be null.. Remember the user stream

        if (!is_array($ids) or count($ids) == 0) {
            throw new AlbumException('Expecting array values, ' . gettype($values) . ' given in ' . __METHOD__, 100);
        }

        try {

            // Get the images
            $images = Image::getMultiple($ids, true);

            // Get the albums
            $albums = [];

            foreach ($images as $k => $v) {
                if ($v['album']['id'] and $v['album']['id'] !== $album_id) {
                    $album_k = $v['album']['id'];
                    if (!array_key_exists($album_k, $albums)) {
                        $albums[$album_k] = [];
                    }
                    $albums[$album_k][] = $v['id'];
                }
            }

            $db = DB::getInstance();
            $db->query('UPDATE `' . DB::getTable('images') . '` SET `image_album_id`=:image_album_id WHERE `image_id` IN (' . implode(',', $ids) . ')');
            $db->bind(':image_album_id', $album_id);
            $exec = $db->exec();
            if ($exec and $db->rowCount() > 0) {
                // Update the new album
                if (!is_null($album_id)) {
                    self::updateImageCount($album_id, $db->rowCount());
                }
                // Update the old albums
                if (count($albums) > 0) {
                    $album_query = '';
                    $album_query_tpl = 'UPDATE `' . DB::getTable('albums') . '` SET `album_image_count` = GREATEST(`album_image_count` - :counter, 0) WHERE `album_id` = :album_id;';
                    foreach ($albums as $k => $v) {
                        $album_query .= strtr($album_query_tpl, [':counter' => count($v), ':album_id' => $k]);
                    }
                    $db = DB::getInstance();
                    $db->query($album_query);
                    $db->exec();
                }
            }
            return $exec;
        } catch (Exception $e) {
            throw new AlbumException($e->getMessage(), 400);
        }
    }

    public static function update($id, $values)
    {
        if (array_key_exists('description', $values)) {
            G\nullify_string($values['description']);
        }
        try {
            return DB::update('albums', $values, ['id' => $id]);
        } catch (Exception $e) {
            throw new AlbumException($e->getMessage(), 400);
        }
    }

    // Delete album, return the number of deleted images
    public static function delete($id)
    {
        try {
            // Get the user id
            $user_id = DB::get('albums', ['id' => $id])[0]['album_user_id'];
            // Get album
            $album = self::getSingle($id);
            if (!$album) {
                return false;
            }
            // Delete album, the easy part
            $delete = DB::delete('albums', ['id' => $id]);
            if (!$delete) {
                return false;
            }
            // Delete album images
            $db = DB::getInstance();
            $db->query('SELECT image_id FROM ' . DB::getTable('images') . ' WHERE image_album_id=:image_album_id');
            $db->bind(':image_album_id', $id);
            $album_image_ids = $db->fetchAll();
            // Delete the files
            $images_deleted = 0;
            foreach ($album_image_ids as $k => $v) {
                if (Image::delete($v['image_id'], false)) { // We will update the user counts (image + album) at once
                    $images_deleted++;
                }
            }
            // Update user
            if ($user_id) {
                $user = User::getSingle($user_id, 'id');
                $user_updated_counts = [
                    'album_count' => '-1',
                    'image_count' => '-' . $images_deleted
                ];
                DB::increment('users', $user_updated_counts, ['id' => $user_id]);
            }
            // Track stats
            Stat::track([
                'action'    => 'delete',
                'table'        => 'albums',
                'value'        => '-1',
                'date_gmt'    => $album['date_gmt']
            ]);
            return $images_deleted;
        } catch (Exception $e) {
            throw new AlbumException($e->getMessage(), 400);
        }
    }

    public static function deleteMultiple($ids)
    {
        if (!is_array($ids)) {
            throw new AlbumException('Expecting array argument, ' . gettype($ids) . ' given in ' . __METHOD__, 100);
        }
        $affected = 0;
        foreach ($ids as $id) {
            $affected += self::delete($id);
        }
        return $affected;
    }

    public static function updateImageCount($id, $counter = 1, $operator = '+')
    {
        try {
            $query = 'UPDATE `' . DB::getTable('albums') . '` SET `album_image_count` = ';
            if (in_array($operator, ['+', '-'])) {
                $query .= 'GREATEST(`album_image_count` ' . $operator . ' ' . $counter . ', 0)';
            } else {
                $query .= $counter;
            }
            $query .= ' WHERE `album_id` = :album_id';
            $db = DB::getInstance();
            $db->query($query);
            $db->bind(':album_id', $id);
            $exec = $db->exec();
            return $exec;
        } catch (Exception $e) {
            throw new AlbumException($e->getMessage(), 400);
        }
    }

    public static function fill(&$album, &$user = [])
    {
        $album['id_encoded'] = $album['id'] ? encodeID($album['id']) : null;
        if ($album['name'] == null && $user['id']) {
            $album['name'] = _s("%s's images", $user['name_short']);
        }
        if ($album['id'] == null) {
            $album['url'] = $user ? User::getUrl($user['username']) : null;
            $album['url_short'] = $album['url'];
        } else {
            $album['url'] = self::getUrl($album['id_encoded'], getSetting('seo_album_urls') ? $album['name'] : '');
            $album['url_short'] = self::getUrl($album['id_encoded'], '');
        }
        $album['name_html'] = G\safe_html($album['name']);
        if ($album['privacy'] == null) {
            $album['privacy'] = "public";
        }
        switch ($album['privacy']) {
            case 'private_but_link':
                $album['privacy_notes'] = _s('Note: This content is private but anyone with the link will be able to see this.');
                break;
            case 'password':
                $album['privacy_notes'] = _s('Note: This content is password protected. Remember to pass the content password to share.');
                break;
            case 'private':
                $album['privacy_notes'] = _s('Note: This content is private. Change privacy to "public" to share.');
                break;
            default:
                $album['privacy_notes'] = null;
                break;
        }

        $private_str = _s('Private');
        $privacy_to_label = [
            'public'            => _s('Public'),
            'private'            => $private_str . '/' . _s('Me'),
            'private_but_link'    => $private_str . '/' . _s('Link'),
            'password'            => $private_str . '/' . _s('Password'),
        ];

        $album['privacy_readable'] = $privacy_to_label[$album['privacy']];
        $album['name_with_privacy_readable'] = $album['name'] . ' (' . $album['privacy_readable'] . ')';
        $album['name_with_privacy_readable_html'] = G\safe_html($album['name_with_privacy_readable']);
        $album['name_truncated'] = G\truncate($album['name'], 28);
        $album['name_truncated_html'] = G\safe_html($album['name_truncated']);

        if (!empty($user)) {
            User::fill($user);
        }
    }

    public static function formatArray($dbrow, $safe = false)
    {
        try {
            $output = DB::formatRow($dbrow);
            self::fill($output, $output['user']);
            $output['views_label'] = _n('view', 'views', $output['views']);
            $output['how_long_ago'] = time_elapsed_string($output['date_gmt']);

            if ($output['images_slice']) {
                foreach ($output['images_slice'] as $k => $v) {
                    $output['images_slice'][$k] = Image::formatArray($output['images_slice'][$k]);
                    $output['images_slice'][$k]['flag'] = $output['images_slice'][$k]['nsfw'] ? 'unsafe' : 'safe';
                }
            }

            if ($safe) {
                unset($output['id'], $output['privacy_extra']);
                unset($output['user']['id']);
            }

            return $output;
        } catch (Exception $e) {
            throw new ImageException($e->getMessage(), 400);
        }
    }

    public static function checkPassword($password, $user_password)
    {
        return G\timing_safe_compare($password, $user_password);
    }

    public static function storeUserPasswordHash($album_id, $user_password)
    {
        $_SESSION['password']['album'][$album_id] = password_hash($user_password, PASSWORD_BCRYPT);
    }

    public static function checkSessionPassword($album = [])
    {
        $user_password_hash = $_SESSION['password']['album'][$album['id']];
        if (!isset($user_password_hash) || !password_verify($album['password'], $user_password_hash)) {
            unset($_SESSION['password']['album'][$album['id']]);
            return false;
        }
        return true;
    }

    // Handle album creation flood
    protected static function handleFlood()
    {
        $logged_user = Login::getUser();
        if (!$logged_user or $logged_user['is_admin']) {
            return false;
        }
        $flood_limit = [
            'minute'    => 20,
            'hour'      => 200,
            'day'       => 400,
            'week'      => 2000,
            'month'     => 10000
        ];
        try {
            $db = DB::getInstance();
            $flood_db = $db->queryFetchSingle(
                "SELECT
			COUNT(IF(album_date_gmt >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 1 MINUTE), 1, NULL)) AS minute,
			COUNT(IF(album_date_gmt >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 1 HOUR), 1, NULL)) AS hour,
			COUNT(IF(album_date_gmt >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 1 DAY), 1, NULL)) AS day,
			COUNT(IF(album_date_gmt >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 1 WEEK), 1, NULL)) AS week,
			COUNT(IF(album_date_gmt >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 1 MONTH), 1, NULL)) AS month
			FROM " . DB::getTable('albums') . " WHERE album_creation_ip='" . G\get_client_ip() . "' AND album_date_gmt >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 1 MONTH)"
            );
        } catch (Exception $e) {
        } // Silence

        $is_flood = false;
        $flood_by = '';
        foreach (['minute', 'hour', 'day', 'week', 'month'] as $v) {
            if ($flood_limit[$v] > 0 and $flood_db[$v] >= $flood_limit[$v]) {
                $flood_by = $v;
                $is_flood = true;
                break;
            }
        }
        if ($is_flood) {
            if (!$_SESSION['flood_albums_notify'][$flood_by]) {
                try {
                    $message_report = '<html><body>' . "\n";
                    $message_report .= strtr('Flooding IP <a href="' . G\get_base_url('search/images/?q=ip:%ip') . '">%ip</a>', ['%ip' => G\get_client_ip()]) . '<br>';
                    $message_report .= 'User <a href="' . $logged_user['url'] . '">' . $logged_user['name'] . '</a><br>';
                    $message_report .= '<br>';
                    $message_report .= '<b>Albums per time period</b>' . "<br>";
                    $message_report .= 'Minute: ' . $flood_db['minute'] . "<br>";
                    $message_report .= 'Hour: ' . $flood_db['hour'] . "<br>";
                    $message_report .= 'Week: ' . $flood_db['day'] . "<br>";
                    $message_report .= 'Month: ' . $flood_db['week'] . "<br>";
                    $message_report .= '</body></html>';
                    send_mail(getSetting('email_incoming_email'), 'Flood report user ID ' . $logged_user['id'], $message_report);
                    $_SESSION['flood_albums_notify'][$flood_by] = true;
                } catch (Exception $e) {
                } // Silence
            }
            return ['flood' => true, 'limit' => $flood_limit[$flood_by], 'count' => $flood_db[$flood_by], 'by' => $flood_by];
        }

        return false;
    }
}

class AlbumException extends Exception
{
}
