<?php

namespace WebPExpress;

use \WebPExpress\Config;
use \WebPExpress\FileHelper;
use \WebPExpress\HTAccessRules;
use \WebPExpress\Paths;
use \WebPExpress\State;

class HTAccess
{

    public static function inlineInstructions($instructions, $marker)
    {
        if ($marker == 'WebP Express') {
            return [];
        } else {
           return $instructions;
        }
    }

    /**
     *  Must be parsed ie "wp-content", "index", etc. Not real dirs
     */
    public static function addToActiveHTAccessDirsArray($dirId)
    {
        $activeHtaccessDirs = State::getState('active-htaccess-dirs', []);
        if (!in_array($dirId, $activeHtaccessDirs)) {
            $activeHtaccessDirs[] = $dirId;
            State::setState('active-htaccess-dirs', array_values($activeHtaccessDirs));
        }
    }

    public static function removeFromActiveHTAccessDirsArray($dirId)
    {
        $activeHtaccessDirs = State::getState('active-htaccess-dirs', []);
        if (in_array($dirId, $activeHtaccessDirs)) {
            $activeHtaccessDirs = array_diff($activeHtaccessDirs, [$dirId]);
            State::setState('active-htaccess-dirs', array_values($activeHtaccessDirs));
        }
    }

    public static function isInActiveHTAccessDirsArray($dirId)
    {
        $activeHtaccessDirs = State::getState('active-htaccess-dirs', []);
        return (in_array($dirId, $activeHtaccessDirs));
    }

    public static function hasRecordOfSavingHTAccessToDir($dir) {
        $dirId = Paths::getAbsDirId($dir);
        if ($dirId !== false) {
            return self::isInActiveHTAccessDirsArray($dirId);
        }
        return false;
    }


    /**
     * @return  string|false  Rules, or false if no rules found or file does not exist.
     */
    public static function extractWebPExpressRulesFromHTAccess($filename) {
        if (FileHelper::fileExists($filename)) {
            $content = FileHelper::loadFile($filename);
            if ($content === false) {
                return false;
            }

            $pos1 = strpos($content, '# BEGIN WebP Express');
            if ($pos1 === false) {
                return false;
            }
            $pos2 = strrpos($content, '# END WebP Express');
            if ($pos2 === false) {
                return false;
            }
            return substr($content, $pos1, $pos2 - $pos1);
        } else {
            // the .htaccess isn't even there. So there are no rules.
            return false;
        }
    }

    /**
     *  Sneak peak into .htaccess to see if we have rules in it
     *  This may not be possible (it requires read permission)
     *  Return true, false, or null if we just can't tell
     */
    public static function haveWeRulesInThisHTAccess($filename) {
        if (FileHelper::fileExists($filename)) {
            $content = FileHelper::loadFile($filename);
            if ($content === false) {
                return null;
            }
            $weRules = (self::extractWebPExpressRulesFromHTAccess($filename));
            if ($weRules === false) {
                return false;
            }

            return (strpos($weRules, '<IfModule ') !== false);
        } else {
            // the .htaccess isn't even there. So there are no rules.
            return false;
        }
    }

    public static function haveWeRulesInThisHTAccessBestGuess($filename)
    {
        // First try to sneak peak. May return null if it cannot be determined.
        $result = self::haveWeRulesInThisHTAccess($filename);
        if ($result === true) {
            return true;
        }
        if ($result === null) {
            // We were not allowed to sneak-peak.
            // Well, good thing that we stored successful .htaccess write locations ;)
            // If we recorded a successful write, then we assume there are still rules there
            // If we did not, we assume there are no rules there
            $dir = FileHelper::dirName($filename);
            return self::hasRecordOfSavingHTAccessToDir($dir);
        }
    }

    public static function getRootsWithWebPExpressRulesIn()
    {
        $allIds = Paths::getImageRootIds();
        $allIds[] = 'cache';
        $result = [];
        foreach ($allIds as $imageRootId) {
            $filename = Paths::getAbsDirById($imageRootId) . '/.htaccess';
            if (self::haveWeRulesInThisHTAccessBestGuess($filename)) {
                $result[] = $imageRootId;
            }

        }
        return $result;
    }

    public static function saveHTAccessRulesToFile($filename, $rules, $createIfMissing = false) {
        if (!@file_exists($filename)) {
            if (!$createIfMissing) {
                return false;
            }
            // insert_with_markers will create file if it doesn't exist, so we can continue...
        }

        $existingFilePermission = null;
        $existingDirPermission = null;

        // Try to make .htaccess writable if its not
        if (@file_exists($filename)) {
            if (!@is_writable($filename)) {
                $existingFilePermission = FileHelper::filePerm($filename);
                @chmod($filename, 0664);        // chmod may fail, we know...
            }
        } else {
            $dir = FileHelper::dirName($filename);
            if (!@is_writable($dir)) {
                $existingDirPermission = FileHelper::filePerm($dir);
                @chmod($dir, 0775);
            }
        }

        /* Add rules to .htaccess  */
        if (!function_exists('insert_with_markers')) {
            require_once ABSPATH . 'wp-admin/includes/misc.php';
        }

        // Convert to array, because string version has bugs in Wordpress 4.3
        $rules = explode("\n", $rules);

        add_filter('insert_with_markers_inline_instructions', array('\WebPExpress\HTAccess', 'inlineInstructions'), 10, 2);

        $success = insert_with_markers($filename, 'WebP Express', $rules);

        // Revert file or dir permissions
        if (!is_null($existingFilePermission)) {
            @chmod($filename, $existingFilePermission);
        }
        if (!is_null($existingDirPermission)) {
            @chmod($dir, $existingDirPermission);
        }

        if ($success) {
            State::setState('htaccess-rules-saved-at-some-point', true);

            //$containsRules = (strpos(implode('',$rules), '# Redirect images to webp-on-demand.php') != false);
            $containsRules = (strpos(implode('',$rules), '<IfModule mod_rewrite.c>') !== false);

            $dir = FileHelper::dirName($filename);
            $dirId = Paths::getAbsDirId($dir);
            if ($dirId !== false) {
                if ($containsRules) {
                    self::addToActiveHTAccessDirsArray($dirId);
                } else {
                    self::removeFromActiveHTAccessDirsArray($dirId);
                }
            }
        }

        return $success;
    }

    public static function saveHTAccessRules($rootId, $rules, $createIfMissing = true) {
        $filename = Paths::getAbsDirById($rootId) . '/.htaccess';
        return self::saveHTAccessRulesToFile($filename, $rules, $createIfMissing);
    }

    /* only called in this file */
    public static function saveHTAccessRulesToFirstWritableHTAccessDir($dirs, $rules)
    {
        foreach ($dirs as $dir) {
            if (self::saveHTAccessRulesToFile($dir . '/.htaccess', $rules, true)) {
                return $dir;
            }
        }
        return false;
    }


    /**
     *  Try to deactivate all .htaccess rules.
     *  If success, we return true.
     *  If we fail, we return an array of filenames that have problems
     *  @return  true|array
     */
    public static function deactivateHTAccessRules($comment = '# Plugin is deactivated') {

        $rootsToClean = Paths::getImageRootIds();
        $rootsToClean[] = 'home';
        $rootsToClean[] = 'cache';
        $failures = [];
        $successes = [];

        foreach ($rootsToClean as $imageRootId) {
            $dir = Paths::getAbsDirById($imageRootId);
            $filename = $dir . '/.htaccess';
            if (!FileHelper::fileExists($filename)) {
                //error_log('exists not:' . $filename);
                continue;
            } else {
                if (self::haveWeRulesInThisHTAccessBestGuess($filename)) {
                    if (self::saveHTAccessRulesToFile($filename, $comment, false)) {
                        $successes[] = $imageRootId;
                    } else {
                        $failures[] = $imageRootId;
                    }
                } else {
                    //error_log('no rules:' . $filename);
                }
            }
        }
        $success =  (count($failures) == 0);
        return [$success, $failures, $successes];
    }

    public static function testLinks($config) {
        /*
        if (isset($_SERVER['HTTP_ACCEPT']) && (strpos($_SERVER['HTTP_ACCEPT'], 'image/webp') !== false )) {
            if ($config['operation-mode'] != 'no-conversion') {
                if ($config['image-types'] != 0) {
                    $webpExpressRoot = Paths::getWebPExpressPluginUrlPath();
                    $links = '';
                    if ($config['enable-redirection-to-converter']) {
                        $links = '<br>';
                        $links .= '<a href="/' . $webpExpressRoot . '/test/test.jpg?debug&time=' . time() . '" target="_blank">Convert test image (show debug)</a><br>';
                        $links .= '<a href="/' . $webpExpressRoot . '/test/test.jpg?' . time() . '" target="_blank">Convert test image</a><br>';
                    }
                    // TODO: webp-realizer test links (to missing webp)
                    if ($config['enable-redirection-to-webp-realizer']) {
                    }

                    // TODO: test link for testing redirection to existing
                    if ($config['redirect-to-existing-in-htaccess']) {

                    }

                    return $links;
                }
            }
        }*/
        return '';
    }


    public static function getHTAccessDirRequirements() {
        $minRequired = 'index';
        if (Paths::isWPContentDirMovedOutOfAbsPath()) {
            $minRequired = 'wp-content';
            $pluginToo = Paths::isPluginDirMovedOutOfWpContent() ? 'yes' : 'no';
            $uploadToo = Paths::isUploadDirMovedOutOfWPContentDir() ? 'yes' : 'no';
        } else {
            // plugin requirement depends...
            // - if user grants access to 'index', the requirement is Paths::isPluginDirMovedOutOfAbsPath()
            // - if user grants access to 'wp-content', the requirement is Paths::isPluginDirMovedOutOfWpContent()
            $pluginToo = 'depends';

            // plugin requirement depends...
            // - if user grants access to 'index', we should be fine, as UPLOADS is always in ABSPATH.
            // - if user grants access to 'wp-content', the requirement is Paths::isUploadDirMovedOutOfWPContentDir()
            $uploadToo = 'depends';
        }

        // We need upload too for rewrite rules when destination structure is image-roots.
        // but it is also good otherwise. So lets always do it.

        $uploadToo = 'yes';

        return [
            $minRequired,
            $pluginToo,      // 'yes', 'no' or 'depends'
            $uploadToo
        ];
    }

    public static function saveRules($config, $showMessage = true) {
        list($success, $failedDeactivations, $successfulDeactivations) = self::deactivateHTAccessRules('# The rules have left the building');

        $rootsToPutRewritesIn = $config['scope'];
        if ($config['destination-structure'] == 'doc-root') {
            // Commented out to quickfix #338
            // $rootsToPutRewritesIn = Paths::filterOutSubRoots($rootsToPutRewritesIn);
        }

        $dirsContainingWebps = [];

        $mingled = ($config['destination-folder'] == 'mingled');
        if ($mingled) {
            $dirsContainingWebps[] = 'uploads';
        }
        $scopeOtherThanUpload = (str_replace('uploads', '', implode(',', $config['scope'])) != '');

        if ($scopeOtherThanUpload || (!$mingled)) {
            $dirsContainingWebps[] = 'cache';
        }

        $dirsToPutRewritesIn = array_unique(array_merge($rootsToPutRewritesIn, $dirsContainingWebps));

        $failedWrites = [];
        $successfullWrites = [];
        foreach ($dirsToPutRewritesIn as $rootId) {
            $dirContainsSourceImages = in_array($rootId, $rootsToPutRewritesIn);
            $dirContainsWebPImages = in_array($rootId, $dirsContainingWebps);

            $rules = HTAccessRules::generateHTAccessRulesFromConfigObj(
                $config,
                $rootId,
                $dirContainsSourceImages,
                $dirContainsWebPImages
            );
            $success = self::saveHTAccessRules(
                $rootId,
                $rules,
                true
            );
            if ($success) {
                $successfullWrites[] = $rootId;

                // Remove it from $successfulDeactivations (if it is there)
                if (($key = array_search($rootId, $successfulDeactivations)) !== false) {
                    unset($successfulDeactivations[$key]);
                }
            } else {
                $failedWrites[] = $rootId;

                // Remove it from $failedDeactivations (if it is there)
                if (($key = array_search($rootId, $failedDeactivations)) !== false) {
                    unset($failedDeactivations[$key]);
                }
            }
        }

        $success = ((count($failedDeactivations) == 0) && (count($failedWrites) == 0));

        $return = [$success, $successfullWrites, $successfulDeactivations, $failedWrites, $failedDeactivations];
        if ($showMessage) {
            self::showSaveRulesMessages($return);
        }
        return $return;
    }

    public static function showSaveRulesMessages($saveRulesResult)
    {
        list($success, $successfullWrites, $successfulDeactivations, $failedWrites, $failedDeactivations) = $saveRulesResult;

        $msg = '';
        if (count($successfullWrites) > 0) {
            $msg .= '<p>Rewrite rules were saved to the following files:</p>';
            foreach ($successfullWrites as $rootId) {
                $msg .= '<i>' . Paths::getAbsDirById($rootId) . '/.htaccess</i> (' . $rootId . ')<br>';
            }
        }

        if (count($successfulDeactivations) > 0) {
            $msg .= '<p>Rewrite rules were removed from the following files:</p>';
            foreach ($successfulDeactivations as $rootId) {
                $msg .= '<i>' . Paths::getAbsDirById($rootId) . '/.htaccess</i> (' . $rootId . ')<br>';
            }
        }

        if ($msg != '') {
            Messenger::addMessage(
                ($success ? 'success' : 'info'),
                $msg
            );
        }

        if (count($failedWrites) > 0) {
            $msg = '<p>Failed writing rewrite rules to the following files:</p>';
            foreach ($failedWrites as $rootId) {
                $msg .= '<i>' . Paths::getAbsDirById($rootId) . '/.htaccess</i> (' . $rootId . ')<br>';
            }
            $msg .= 'You need to change the file permissions to allow WebP Express to save the rules.';
            Messenger::addMessage('error', $msg);
        } else {
            if (count($failedDeactivations) > 0) {
                $msg = '<p>Failed deleting unused rewrite rules in the following files:</p>';
                foreach ($failedDeactivations as $rootId) {
                    $msg .= '<i>' . Paths::getAbsDirById($rootId) . '/.htaccess</i> (' . $rootId . ')<br>';
                }
                $msg .= 'You need to change the file permissions to allow WebP Express to remove the rules or ' .
                    'remove them manually';
                Messenger::addMessage('error', $msg);
            }
        }
    }

}
