<?php
namespace Misuzu;

use stdClass;

// All permissions should be defined in this class
// To avoid future conflicts, unused/deprecated permissions should remain defined for any given category
final class Perm {
    // GLOBAL ALLOCATION:
    // 0bXXXXX_XXXXXXXX_CCCCCCCC_FFFFFFFF_NNNNNNNN_LLLLLLLL_GGGGGGGG
    //       G -> General global permissions
    //      L -> Changelog permissions
    //     N -> News permissions
    //    F -> Global Forum perms
    //   C -> Global Comments perms
    //  X -> unallocated
    public const G_IS_JANITOR                = 0b00000_00000000_00000000_00000000_00000000_00000000_00000001;
    public const G_LOGS_VIEW                 = 0b00000_00000000_00000000_00000000_00000000_00000000_00000010;
    public const G_EMOTES_MANAGE             = 0b00000_00000000_00000000_00000000_00000000_00000000_00000100;
    public const G_CONFIG_MANAGE             = 0b00000_00000000_00000000_00000000_00000000_00000000_00001000;
    //public const G_IS_TESTER               = 0b00000_00000000_00000000_00000000_00000000_00000000_00010000; // deprecated: tester status went unused
    public const G_BLACKLIST_MANAGE          = 0b00000_00000000_00000000_00000000_00000000_00000000_00100000; // unused: blacklist is currently removed to reduce overhead and it seemed like it was broken
    //public const G_TWITTER_MANAGE          = 0b00000_00000000_00000000_00000000_00000000_00000000_01000000; // deprecated: twitter integration has been removed
    public const G_TIMINGS_VIEW              = 0b00000_00000000_00000000_00000000_00000000_00000000_10000000;

    public const G_CL_CHANGES_MANAGE         = 0b00000_00000000_00000000_00000000_00000000_00000001_00000000;
    public const G_CL_TAGS_MANAGE            = 0b00000_00000000_00000000_00000000_00000000_00000010_00000000;
    //public const G_CL_ACTIONS_MANAGE       = 0b00000_00000000_00000000_00000000_00000000_00000100_00000000; // deprecated: actions are hardcoded now

    public const G_NEWS_POSTS_MANAGE         = 0b00000_00000000_00000000_00000000_00000001_00000000_00000000;
    public const G_NEWS_CATEGORIES_MANAGE    = 0b00000_00000000_00000000_00000000_00000010_00000000_00000000;

    public const G_FORUM_CATEGORIES_MANAGE   = 0b00000_00000000_00000000_00000001_00000000_00000000_00000000;
    public const G_FORUM_LEADERBOARD_VIEW    = 0b00000_00000000_00000000_00000010_00000000_00000000_00000000;
    public const G_FORUM_TOPIC_REDIRS_MANAGE = 0b00000_00000000_00000000_00000100_00000000_00000000_00000000;

    public const G_COMMENTS_CREATE           = 0b00000_00000000_00000001_00000000_00000000_00000000_00000000;
    public const G_COMMENTS_EDIT_OWN         = 0b00000_00000000_00000010_00000000_00000000_00000000_00000000; // unused: editing not implemented
    public const G_COMMENTS_EDIT_ANY         = 0b00000_00000000_00000100_00000000_00000000_00000000_00000000; // unused: editing not implemented
    public const G_COMMENTS_DELETE_OWN       = 0b00000_00000000_00001000_00000000_00000000_00000000_00000000;
    public const G_COMMENTS_DELETE_ANY       = 0b00000_00000000_00010000_00000000_00000000_00000000_00000000;
    public const G_COMMENTS_PIN              = 0b00000_00000000_00100000_00000000_00000000_00000000_00000000;
    public const G_COMMENTS_LOCK             = 0b00000_00000000_01000000_00000000_00000000_00000000_00000000;
    public const G_COMMENTS_VOTE             = 0b00000_00000000_10000000_00000000_00000000_00000000_00000000;

    // USER ALLOCATION:
    // There's no rules here, manage perms started abouts halfway through the 31-bit integer
    // Maybe formally define the octets regardless later?
    public const U_PROFILE_EDIT              = 0b00000_00000000_00000000_00000000_00000000_00000000_00000001;
    public const U_AVATAR_CHANGE             = 0b00000_00000000_00000000_00000000_00000000_00000000_00000010;
    public const U_PROFILE_BACKGROUND_CHANGE = 0b00000_00000000_00000000_00000000_00000000_00000000_00000100;
    public const U_PROFILE_ABOUT_EDIT        = 0b00000_00000000_00000000_00000000_00000000_00000000_00001000;
    public const U_PROFILE_BIRTHDATE_EDIT    = 0b00000_00000000_00000000_00000000_00000000_00000000_00010000;
    public const U_FORUM_SIGNATURE_EDIT      = 0b00000_00000000_00000000_00000000_00000000_00000000_00100000;
    public const U_USERS_MANAGE              = 0b00000_00000000_00000000_00000000_00010000_00000000_00000000;
    public const U_ROLES_MANAGE              = 0b00000_00000000_00000000_00000000_00100000_00000000_00000000;
    public const U_PERMS_MANAGE              = 0b00000_00000000_00000000_00000000_01000000_00000000_00000000;
    public const U_REPORTS_MANAGE            = 0b00000_00000000_00000000_00000000_10000000_00000000_00000000; // unused: reports are not implemented
    public const U_WARNINGS_MANAGE           = 0b00000_00000000_00000000_00000001_00000000_00000000_00000000;
    //public const U_BLACKLISTS_MANAGE       = 0b00000_00000000_00000000_00000010_00000000_00000000_00000000; // deprecated: replaced with GLOBAL_BLACKLIST_MANAGE
    public const U_NOTES_MANAGE              = 0b00000_00000000_00000000_00000100_00000000_00000000_00000000;
    public const U_BANS_MANAGE               = 0b00000_00000000_00000000_00001000_00000000_00000000_00000000;
    public const U_CAN_IMPERSONATE           = 0b00000_00000000_00000000_00010000_00000000_00000000_00000000;

    // FORUM ALLOCATION:
    // 0bXXXXX_XXXXXXXX_XXXXXXXX_PPPPPPPP_PPPPTTTT_TTTTTTTT_CCCCCCCC
    //     C -> Category related
    //    T -> Topic related
    //   N -> Post related
    //  X -> unallocated
    public const F_CATEGORY_LIST         = 0b00000_00000000_00000000_00000000_00000000_00000000_00000001;
    public const F_CATEGORY_VIEW         = 0b00000_00000000_00000000_00000000_00000000_00000000_00000010;
    public const F_TOPIC_CREATE          = 0b00000_00000000_00000000_00000000_00000000_00000100_00000000;
    //public const F_TOPIC_DELETE        = 0b00000_00000000_00000000_00000000_00000000_00001000_00000000; // deprecated: use F_POST_DELETE_ANY instead
    public const F_TOPIC_MOVE            = 0b00000_00000000_00000000_00000000_00000000_00010000_00000000; // unused: topic moving not implemented
    public const F_TOPIC_LOCK            = 0b00000_00000000_00000000_00000000_00000000_00100000_00000000;
    public const F_TOPIC_STICKY          = 0b00000_00000000_00000000_00000000_00000000_01000000_00000000;
    public const F_TOPIC_ANNOUNCE_LOCAL  = 0b00000_00000000_00000000_00000000_00000000_10000000_00000000;
    public const F_TOPIC_ANNOUNCE_GLOBAL = 0b00000_00000000_00000000_00000000_00000001_00000000_00000000;
    public const F_TOPIC_BUMP            = 0b00000_00000000_00000000_00000000_00000010_00000000_00000000;
    public const F_TOPIC_PRIORITY_VOTE   = 0b00000_00000000_00000000_00000000_00000100_00000000_00000000; // unused: feature postponed, reuse if it makes sense otherwise deprecate
    public const F_POST_CREATE           = 0b00000_00000000_00000000_00000000_00010000_00000000_00000000;
    public const F_POST_EDIT_OWN         = 0b00000_00000000_00000000_00000000_00100000_00000000_00000000;
    public const F_POST_EDIT_ANY         = 0b00000_00000000_00000000_00000000_01000000_00000000_00000000;
    public const F_POST_DELETE_OWN       = 0b00000_00000000_00000000_00000000_10000000_00000000_00000000;
    public const F_POST_DELETE_ANY       = 0b00000_00000000_00000000_00000001_00000000_00000000_00000000;

    // CHAT ALLOCATION:
    // 0bXXXXX_XXXXXXXX_XXXXXXXX_XXXXXXXX_XXXXXXMA_MMUMUUMT_ATATAMMM
    // U -> Normal user permissions
    // T -> Tenshi permissions
    // M -> Moderator permissions
    // A -> Administrator permissions
    // no staircase here because it's a mess
    public const C_USER_KICK            = 0b00000_00000000_00000000_00000000_00000000_00000000_00000001;
    public const C_USER_BAN             = 0b00000_00000000_00000000_00000000_00000000_00000000_00000010;
    //public const C_USER_SILENCE       = 0b00000_00000000_00000000_00000000_00000000_00000000_00000100; // deprecated: silencing is no longer a thing
    public const C_MESSAGE_BROADCAST    = 0b00000_00000000_00000000_00000000_00000000_00000000_00001000;
    public const C_NICK_SET_OWN         = 0b00000_00000000_00000000_00000000_00000000_00000000_00010000;
    public const C_NICK_SET_ANY         = 0b00000_00000000_00000000_00000000_00000000_00000000_00100000;
    public const C_CHANNEL_CREATE       = 0b00000_00000000_00000000_00000000_00000000_00000000_01000000;
    public const C_CHANNEL_SET_PERSIST  = 0b00000_00000000_00000000_00000000_00000000_00000000_10000000;
    public const C_CHANNEL_SET_PASSWORD = 0b00000_00000000_00000000_00000000_00000000_00000001_00000000;
    public const C_CHANNEL_SET_MIN_RANK = 0b00000_00000000_00000000_00000000_00000000_00000010_00000000;
    public const C_MESSAGE_SEND         = 0b00000_00000000_00000000_00000000_00000000_00000100_00000000;
    public const C_MESSAGE_DELETE_OWN   = 0b00000_00000000_00000000_00000000_00000000_00001000_00000000;
    public const C_MESSAGE_DELETE_ANY   = 0b00000_00000000_00000000_00000000_00000000_00010000_00000000;
    public const C_MESSAGE_EDIT_OWN     = 0b00000_00000000_00000000_00000000_00000000_00100000_00000000;
    public const C_MESSAGE_EDIT_ANY     = 0b00000_00000000_00000000_00000000_00000000_01000000_00000000;
    public const C_USER_VIEW_ADDR       = 0b00000_00000000_00000000_00000000_00000000_10000000_00000000;
    public const C_CHANNEL_DELETE       = 0b00000_00000000_00000000_00000000_00000001_00000000_00000000;
    public const C_CHANNEL_JOIN_ANY     = 0b00000_00000000_00000000_00000000_00000010_00000000_00000000;

    public const INFO_FOR_USER = ['global', 'user', 'chat'];
    public const INFO_FOR_ROLE = self::INFO_FOR_USER; // just alias for now, no clue if this will ever desync
    public const INFO_FOR_FORUM_CATEGORY = ['forum'];

    public const LISTS_FOR_USER = ['global:general', 'global:changelog', 'global:news', 'global:forum', 'global:comments', 'user:personal', 'user:manage', 'chat:general'];
    public const LISTS_FOR_ROLE = self::LISTS_FOR_USER; // idem
    public const LISTS_FOR_FORUM_CATEGORY = ['forum:category', 'forum:topic', 'forum:post'];

    public const LISTS = [
        'global:general' => [
            'title' => 'Global Permissions',
            'perms' => [
                'global',
                self::G_IS_JANITOR,
                self::G_LOGS_VIEW,
                self::G_EMOTES_MANAGE,
                self::G_CONFIG_MANAGE,
                self::G_BLACKLIST_MANAGE,
                self::G_TIMINGS_VIEW,
            ],
        ],

        'global:changelog' => [
            'title' => 'Changelog Permissions',
            'perms' => [
                'global',
                self::G_CL_CHANGES_MANAGE,
                self::G_CL_TAGS_MANAGE,
            ],
        ],

        'global:news' => [
            'title' => 'News Permissions',
            'perms' => [
                'global',
                self::G_NEWS_POSTS_MANAGE,
                self::G_NEWS_CATEGORIES_MANAGE,
            ],
        ],

        'global:forum' => [
            'title' => 'Global Forum Permissions',
            'perms' => [
                'global',
                self::G_FORUM_CATEGORIES_MANAGE,
                self::G_FORUM_LEADERBOARD_VIEW,
                self::G_FORUM_TOPIC_REDIRS_MANAGE,
            ],
        ],

        'global:comments' => [
            'title' => 'Comments Permissions',
            'perms' => [
                'global',
                self::G_COMMENTS_CREATE,
                self::G_COMMENTS_EDIT_OWN,
                self::G_COMMENTS_EDIT_ANY,
                self::G_COMMENTS_DELETE_OWN,
                self::G_COMMENTS_DELETE_ANY,
                self::G_COMMENTS_PIN,
                self::G_COMMENTS_LOCK,
                self::G_COMMENTS_VOTE,
            ],
        ],

        'user:personal' => [
            'title' => 'User Permissions',
            'perms' => [
                'user',
                self::U_PROFILE_EDIT,
                self::U_AVATAR_CHANGE,
                self::U_PROFILE_BACKGROUND_CHANGE,
                self::U_PROFILE_ABOUT_EDIT,
                self::U_FORUM_SIGNATURE_EDIT,
                self::U_PROFILE_BIRTHDATE_EDIT,
            ],
        ],

        'user:manage' => [
            'title' => 'User Management Permissions',
            'perms' => [
                'user',
                self::U_REPORTS_MANAGE,
                self::U_NOTES_MANAGE,
                self::U_WARNINGS_MANAGE,
                self::U_BANS_MANAGE,
                self::U_USERS_MANAGE,
                self::U_ROLES_MANAGE,
                self::U_PERMS_MANAGE,
                self::U_CAN_IMPERSONATE,
            ],
        ],

        'forum:category' => [
            'title' => 'Forum Category Permissions',
            'perms' => [
                'forum',
                self::F_CATEGORY_LIST,
                self::F_CATEGORY_VIEW,
            ],
        ],

        'forum:topic' => [
            'title' => 'Forum Topic Permissions',
            'perms' => [
                'forum',
                self::F_TOPIC_CREATE,
                self::F_TOPIC_MOVE,
                self::F_TOPIC_LOCK,
                self::F_TOPIC_STICKY,
                self::F_TOPIC_ANNOUNCE_LOCAL,
                self::F_TOPIC_ANNOUNCE_GLOBAL,
                self::F_TOPIC_BUMP,
                self::F_TOPIC_PRIORITY_VOTE,
            ],
        ],

        'forum:post' => [
            'title' => 'Forum Topic Permissions',
            'perms' => [
                'forum',
                self::F_POST_CREATE,
                self::F_POST_EDIT_OWN,
                self::F_POST_EDIT_ANY,
                self::F_POST_DELETE_OWN,
                self::F_POST_DELETE_ANY,
            ],
        ],

        'chat:general' => [
            'title' => 'Chat Permissions',
            'perms' => [
                'chat',
                self::C_MESSAGE_SEND,
                self::C_MESSAGE_BROADCAST,
                self::C_MESSAGE_EDIT_OWN,
                self::C_MESSAGE_EDIT_ANY,
                self::C_MESSAGE_DELETE_OWN,
                self::C_MESSAGE_DELETE_ANY,

                self::C_NICK_SET_OWN,
                self::C_NICK_SET_ANY,

                self::C_USER_KICK,
                self::C_USER_BAN,
                self::C_USER_VIEW_ADDR,

                self::C_CHANNEL_CREATE,
                self::C_CHANNEL_SET_PERSIST,
                self::C_CHANNEL_SET_PASSWORD,
                self::C_CHANNEL_SET_MIN_RANK,
                self::C_CHANNEL_JOIN_ANY,
                self::C_CHANNEL_DELETE,
            ],
        ],
    ];

    public const LABELS = [
        'global' => [
            self::G_IS_JANITOR => 'Can access the Broom closet.',
            self::G_LOGS_VIEW => 'Can view global audit logs.',
            self::G_EMOTES_MANAGE => 'Can manage emoticons.',
            self::G_CONFIG_MANAGE => 'Can manage global configuration.',
            //self::G_IS_TESTER => 'Can test experimental features.',
            self::G_BLACKLIST_MANAGE => 'Can manage registration IP address blacklist.',
            //self::G_TWITTER_MANAGE => 'Can manage Twitter integration settings.',
            self::G_TIMINGS_VIEW => 'Can view page load timings.',

            self::G_CL_CHANGES_MANAGE => 'Can manage changelog entries.',
            self::G_CL_TAGS_MANAGE => 'Can manage changelog tags.',
            //self::G_CL_ACTIONS_MANAGE => 'Can manage changelog action types.',

            self::G_NEWS_POSTS_MANAGE => 'Can manage news posts.',
            self::G_NEWS_CATEGORIES_MANAGE => 'Can manage news categories.',

            self::G_FORUM_CATEGORIES_MANAGE => 'Can manage forum categories.',
            self::G_FORUM_LEADERBOARD_VIEW => 'Can view forum leaderboard.',
            self::G_FORUM_TOPIC_REDIRS_MANAGE => 'Can create redirects for deleted forum topics.',

            self::G_COMMENTS_CREATE => 'Can post comments.',
            self::G_COMMENTS_EDIT_OWN => 'Can edit own comments.',
            self::G_COMMENTS_EDIT_ANY => 'Can edit ANY comment.',
            self::G_COMMENTS_DELETE_OWN => 'Can delete own comments.',
            self::G_COMMENTS_DELETE_ANY => 'Can delete ANY comment.',
            self::G_COMMENTS_PIN => 'Can pin commments.',
            self::G_COMMENTS_LOCK => 'Can lock comment categories.',
            self::G_COMMENTS_VOTE => 'Can vote (like or dislike) on comments.',
        ],

        'user' => [
            self::U_PROFILE_EDIT => 'Can edit own profile.',
            self::U_AVATAR_CHANGE => 'Can change own avatar.',
            self::U_PROFILE_BACKGROUND_CHANGE => 'Can change own profile background.',
            self::U_PROFILE_ABOUT_EDIT => 'Can edit own profile about section.',
            self::U_PROFILE_BIRTHDATE_EDIT => 'Can edit own profile birthdate.',
            self::U_FORUM_SIGNATURE_EDIT => 'Can edit own forum signature.',

            self::U_USERS_MANAGE => 'Can manage other users.',
            self::U_ROLES_MANAGE => 'Can manage user roles.',
            self::U_PERMS_MANAGE => 'Can manage permissions.',
            self::U_REPORTS_MANAGE => 'Can handle reports.',
            self::U_WARNINGS_MANAGE => 'Can manage user warnings.',
            //self::U_BLACKLISTS_MANAGE => 'Can manage registration IP address blacklist.',
            self::U_NOTES_MANAGE => 'Can manage user notes.',
            self::U_BANS_MANAGE => 'Can manage user bans.',
            self::U_CAN_IMPERSONATE => 'Can impersonate select other users. Requires whitelisting in the configuration or super user status.',
        ],

        'forum' => [
            self::F_CATEGORY_LIST => 'Can see the forum category listed but cannot access it.',
            self::F_CATEGORY_VIEW => 'Can access the forum category.',

            self::F_TOPIC_CREATE => 'Can create forum topics.',
            //self::F_TOPIC_DELETE => 'Can delete forum topics.',
            self::F_TOPIC_MOVE => 'Can move forum topics to other categories.',
            self::F_TOPIC_LOCK => 'Can lock forum topics.',
            self::F_TOPIC_STICKY => 'Can make sticky topics.',
            self::F_TOPIC_ANNOUNCE_LOCAL => 'Can make local announcement topics.',
            self::F_TOPIC_ANNOUNCE_GLOBAL => 'Can make global announcement topics.',
            self::F_TOPIC_BUMP => 'Can bump topics without posting a reply.',
            self::F_TOPIC_PRIORITY_VOTE => 'Can use the priority voting system.',

            self::F_POST_CREATE => 'Can create forum posts (replies only, if not allowed to create topics).',
            self::F_POST_EDIT_OWN => 'Can edit own forum posts.',
            self::F_POST_EDIT_ANY => 'Can edit ANY forum post.',
            self::F_POST_DELETE_OWN => 'Can delete own forum posts.',
            self::F_POST_DELETE_ANY => 'Can delete ANY forum post.',
        ],

        'chat' => [
            self::C_USER_KICK => 'Can kick other users.',
            self::C_USER_BAN => 'Can ban other users.',
            //self::C_USER_SILENCE => 'Can silence other users.',
            self::C_MESSAGE_BROADCAST => 'Can broadcast messages across all channels.',
            self::C_NICK_SET_OWN => 'Can change own nickname.',
            self::C_NICK_SET_ANY => 'Can change ANYONE\'s nickname.',
            self::C_CHANNEL_CREATE => 'Can create channels.',
            self::C_CHANNEL_SET_PERSIST => 'Can set channels to persist after all users leave it.',
            self::C_CHANNEL_SET_PASSWORD => 'Can set passwords for own channel.',
            self::C_CHANNEL_SET_MIN_RANK => 'Can set minimum requires rank to join channel.',
            self::C_MESSAGE_SEND => 'Can send messages.',
            self::C_MESSAGE_DELETE_OWN => 'Can delete own messages.',
            self::C_MESSAGE_DELETE_ANY => 'Can delete ANY message.',
            self::C_MESSAGE_EDIT_OWN => 'Can edit own messages.',
            self::C_MESSAGE_EDIT_ANY => 'Can edit ANY message.',
            self::C_USER_VIEW_ADDR => 'Can view IP addresses of other users.',
            self::C_CHANNEL_DELETE => 'Can delete own channels.',
            self::C_CHANNEL_JOIN_ANY => 'Can join any channel regardless of join requirements.',
        ],
    ];

    public static function label(string $category, int $permission): string {
        return array_key_exists($category, self::LABELS)
            && array_key_exists($permission, self::LABELS[$category])
            ? self::LABELS[$category][$permission] : '';
    }

    public static function createList(array $sections): array {
        $list = [];

        foreach($sections as $sectionName) {
            if(!array_key_exists($sectionName, self::LISTS))
                continue;

            $currentCategoryName = '';
            $sectionInfo = self::LISTS[$sectionName];

            $list[] = $item = new stdClass;
            $item->name = $sectionName;
            $item->title = $sectionInfo['title'];
            $item->perms = [];

            foreach($sectionInfo['perms'] as $permInfo) {
                if(is_string($permInfo)) {
                    $currentCategoryName = $permInfo;
                    continue;
                }

                $categoryName = $currentCategoryName;
                $perm = 0;
                // if(is_array($permInfo))
                //     [$categoryName, $perm] = $permInfo;
                // else
                if(is_int($permInfo))
                    $perm = $permInfo;

                $item->perms[] = $permItem = new stdClass;
                $permItem->category = $categoryName;
                $permItem->name = sprintf('perms[%s:%d]', $categoryName, $perm);
                $permItem->title = self::label($categoryName, $perm);
                $permItem->value = $perm;
            }
        }

        return $list;
    }

    public static function convertSubmission(array $raw, array $categories): array {
        $apply = [];

        foreach($raw as $name => $mode) {
            $nameParts = explode(':', $name, 2);
            if(count($nameParts) !== 2 || !ctype_alpha($nameParts[0]) || !ctype_digit($nameParts[1]))
                continue;

            [$category, $value] = $nameParts;
            if(!in_array($category, $categories))
                continue;

            if($mode === 'yes' || $mode === 'never') {
                if(!array_key_exists($category, $apply))
                    $apply[$category] = ['allow' => 0, 'deny' => 0];

                $apply[$category][$mode === 'yes' ? 'allow' : 'deny'] |= (int)$value;
            }
        }

        return $apply;
    }
}
