<?php

namespace Twnt4All\Engine\Support;

class SemaphoreLock
{
    protected $key  = null;
    protected $file = null;
    protected $own  = false;

    function __construct( $key, $dir )
    {
        $this->key = $key;

        //create a new resource or get exisitng with same key
        $locks_dir = $dir . 'locks' . DIRECTORY_SEPARATOR;
        if ( !file_exists( $locks_dir ) ) {
            // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_mkdir -- Filesystem semaphore lock directory; WP_Filesystem does not preserve the file handle required for flock().
            mkdir( $locks_dir, 0775 );
            file_put_contents( $locks_dir . '.htaccess', 'deny from all' );
        }

        $path       = $locks_dir . "$key.lockfile";
        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- Need a real PHP resource handle to call flock() for inter-process synchronization; WP_Filesystem returns strings, not file handles.
        $this->file = fopen( $path, 'w+' );
    }

    function __destruct()
    {
        if ( $this->own == true )
            $this->unlock();
    }

    function lock()
    {
        if ( !flock( $this->file, LOCK_EX | LOCK_NB ) ) { //failed
            $key = $this->key;
            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- semaphore contention warning is intentionally routed to PHP error log so it surfaces during production incidents.
            error_log( "ExclusiveLock::acquire_lock FAILED to acquire lock [$key]" );
            return false;
        }
        ftruncate( $this->file, 0 ); // truncate file
        //write something to just help debugging
        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite -- Writes through the flock()-locked resource; WP_Filesystem cannot reuse the locked file handle.
        fwrite( $this->file, "Locked\n" );
        fflush( $this->file );

        $this->own = true;
        return TRUE; // success
    }

    function unlock()
    {
        $key = $this->key;
        if ( $this->own == true ) {
            if ( !flock( $this->file, LOCK_UN ) ) { //failed
                // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- semaphore release failure is intentionally surfaced via the PHP error log.
                error_log( "ExclusiveLock::lock FAILED to release lock [$key]" );
                return false;
            }
            ftruncate( $this->file, 0 ); // truncate file
            //write something to just help debugging
            // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite -- Writes through the flock()-locked resource; WP_Filesystem cannot reuse the locked file handle.
            fwrite( $this->file, "Unlocked\n" );
            fflush( $this->file );
            $this->own = false;
        } else {
            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- misuse warning intentionally surfaced via the PHP error log.
            error_log( "ExclusiveLock::unlock called on [$key] but its not acquired by caller" );
        }
        return TRUE; // success
    }

    /**
     * Removes all locks starting with a certain prefix from directory $dir.
     *
     * @param $prefix
     * @param $dir
     */
    public static function remove_all_locks( $prefix, $dir )
    {
        $locks_dir = $dir . 'locks' . DIRECTORY_SEPARATOR;
        $files     = preg_grep( '~^' . $prefix . '.*~', scandir( $locks_dir ) );

        if ( !empty( $files ) ) {
            foreach ( $files as $file ) {
                wp_delete_file( $locks_dir . $file );
            }
        }

    }
}

;