SiteRank::getRankTitle()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 3
eloc 4
c 2
b 0
f 0
nc 3
nop 1
dl 0
loc 8
rs 10
1
<?php
2
3
namespace App\Models\Eloquent\Tool;
4
5
use App\Models\Eloquent\ProblemSolution;
6
use App\Models\Eloquent\Submission;
7
use App\Models\Eloquent\User;
8
use Arr;
9
use Cache;
10
use Carbon;
11
use DB;
12
13
class SiteRank
14
{
15
    private static $professionalRanking = [
16
        "Legendary Grandmaster" => "cm-colorful-text",
17
        "International Grandmaster" => "wemd-pink-text",
18
        "Grandmaster" => "wemd-red-text",
19
        "International Master" => "wemd-amber-text",
20
        "Master" => "wemd-orange-text",
21
        "Candidate Master" => "wemd-purple-text",
22
        "Expert" => "wemd-blue-text",
23
        "Specialist" => "wemd-cyan-text",
24
        "Pupil" => "wemd-green-text",
25
        "Newbie" => "wemd-gray-text",
26
    ];
27
28
    private static $professionalRankingPer = [
29
        "Legendary Grandmaster" => 3000,
30
        "International Grandmaster" => 2600,
31
        "Grandmaster" => 2400,
32
        "International Master" => 2300,
33
        "Master" => 2100,
34
        "Candidate Master" => 1900,
35
        "Expert" => 1600,
36
        "Specialist" => 1400,
37
        "Pupil" => 1200,
38
        "Newbie" => 1,
39
    ];
40
41
    private static $casualRanking = [
42
        "Fleet Admiral" => "cm-colorful-text",
43
        "Admiral" => "wemd-pink-text",
44
        "Vice Admiral" => "wemd-red-text",
45
        "Captain" => "wemd-deep-orange-text",
46
        "Commander" => "wemd-orange-text",
47
        "Lieutenant Commander" => "wemd-purple-text",
48
        "Lieutenant" => "wemd-blue-text",
49
        "Ensign" => "wemd-cyan-text",
50
        "Apprentice" => "wemd-green-text",
51
        "Recruit" => "wemd-gray-text",
52
    ];
53
54
    private static $casualRankingPer = [
55
        "Fleet Admiral" => 1,
56
        "Admiral" => 5,
57
        "Vice Admiral" => 10,
58
        "Captain" => 10,
59
        "Commander" => 50,
60
        "Lieutenant Commander" => 100,
61
        "Lieutenant" => 300,
62
        "Ensign" => 700,
63
        "Apprentice" => 1000,
64
        "Recruit" => 400,
65
    ];
66
67
    public static function getColor($rankTitle)
68
    {
69
        if (is_null($rankTitle)) {
70
            return "";
71
        }
72
        return self::$casualRanking[$rankTitle];
73
    }
74
75
    public static function getProfessionalColor($rankTitle)
76
    {
77
        if (is_null($rankTitle)) {
78
            return self::$professionalRanking["None"];
79
        }
80
        return self::$professionalRanking[$rankTitle];
81
    }
82
83
    public static function list($num)
84
    {
85
        $rankList = Cache::tags(['rank'])->get('general');
86
        if (blank($rankList)) {
87
            $rankList = [];
88
        }
89
        $rankList = collect($rankList)->slice(0, $num);
90
        $userIDArr = $rankList->pluck('uid');
91
        $userInfoRaw = User::whereIntegerInRaw('id', $userIDArr)->get();
92
        $userInfo = [];
93
        foreach ($userInfoRaw as $u) {
94
            $userInfo[$u->id] = $u;
95
        }
96
        return $rankList->map(function ($item) use ($userInfo) {
97
            $item["details"] = isset($userInfo[$item["uid"]]) ? $userInfo[$item["uid"]] : [];
98
            return $item;
99
        });
100
    }
101
102
    private static function getRecords(Carbon $from = null)
103
    {
104
        $userAcceptedRecords = Submission::select("uid", DB::raw("count(distinct pid) as solved"))->where("verdict", "Accepted");
105
        $userCommunityRecords = ProblemSolution::select("uid", DB::raw("count(distinct pid) as community"))->where("audit", 1);
106
        if(filled($from)){
107
            $userAcceptedRecords = $userAcceptedRecords->where("submission_date", ">", $from->timestamp);
108
            $userCommunityRecords = $userCommunityRecords->where("created_at", ">", $from);
109
        }
110
        $userAcceptedRecords = collect($userAcceptedRecords->groupBy("uid")->get()->toArray());
111
        $userCommunityRecords = collect($userCommunityRecords->groupBy("uid")->get()->toArray());
112
        $totUserRecords = $userAcceptedRecords->pluck('uid')->merge($userCommunityRecords->pluck('uid'))->unique();
113
        $rankList = [];
114
        foreach($totUserRecords as $uid) {
115
            $rankList[$uid]['uid'] = $uid;
116
            $rankList[$uid]['solved'] = 0;
117
            $rankList[$uid]['community'] = 0;
118
            $rankList[$uid]['tot'] = 0;
119
        }
120
        foreach($userAcceptedRecords as $userAcceptedRecord) {
121
            $rankList[$userAcceptedRecord['uid']]['solved'] = $userAcceptedRecord['solved'];
122
        }
123
        foreach($userCommunityRecords as $userCommunityRecord) {
124
            $rankList[$userCommunityRecord['uid']]['community'] = $userCommunityRecord['community'];
125
        }
126
        foreach($rankList as &$rankItem) {
127
            $rankItem['tot'] = $rankItem['solved'] + $rankItem['community'];
128
        }
129
        unset($rankItem);
130
        return $rankList;
131
    }
132
133
    private static function parseCoefficient($rankList)
134
    {
135
        $activityCoefficient = self::getRecords(Carbon::parse('-1 months'));
136
        $activityCoefficientDivider = collect($activityCoefficient)->max('tot');
137
        if(blank($activityCoefficientDivider)) {
138
            $activityCoefficientDivider = 1;
139
        }
140
        foreach ($rankList as $uid => $rankItem) {
141
            if(isset($activityCoefficient[$uid])){
142
                $activityTot = $activityCoefficient[$uid]['tot'];
143
            } else {
144
                $activityTot = 0;
145
            }
146
            $rankList[$uid]["activityCoefficient"] = ($activityTot / $activityCoefficientDivider) + 0.5;
147
            $rankList[$uid]["points"] = $rankList[$uid]["tot"] * $rankList[$uid]["activityCoefficient"];
148
        }
149
        usort($rankList, function($a, $b) {
150
            return $b['points'] <=> $a['points'];
151
        });
152
        return collect($rankList);
153
    }
154
155
    public static function isTopOneHundred($rank)
156
    {
157
        return (1 <= $rank && $rank <= 100);
158
    }
159
160
    public static function getRankString($rank)
161
    {
162
        return filled($rank) ? "#$rank" : "unrated";
163
    }
164
165
    private static function sendMessage($userID, $currentRank, $originalRank)
166
    {
167
        if(self::isTopOneHundred($currentRank)) {
168
            $title = __('message.rank.up.title');
169
            $level = 1;
170
        } else {
171
            $title = __('message.rank.down.title');
172
            $level = 2;
173
        }
174
175
        return sendMessage([
176
            'sender'   => config('app.official_sender'),
177
            'receiver' => $userID,
178
            'title'    => $title,
179
            'type'     => 6,
180
            'level'    => $level,
181
            'data'     => [
182
                'currentRank'  => $currentRank,
183
                'originalRank' => $originalRank,
184
            ]
185
        ]);
186
    }
187
188
    public static function rankList()
189
    {
190
        $originalRankList = self::list(100);
191
        Cache::tags(['rank'])->flush();
192
        $rankList = self::getRecords();
193
        $totUsers = count($rankList);
194
        if ($totUsers > 0) {
195
            // $rankList = DB::select("SELECT *,solvedCount+communityCount as totValue, 1 as activityCoefficient FROM (SELECT uid,sum(solvedCount) as solvedCount,sum(communityCount) as communityCount FROM ((SELECT uid,count(DISTINCT submission.pid) as solvedCount,0 as communityCount from submission where verdict=\"Accepted\" group by uid) UNION (SELECT uid,0 as solvedCount,count(DISTINCT pid) from problem_solution where audit=1 group by uid)) as temp GROUP BY uid) as temp2 ORDER BY solvedCount+communityCount DESC");
196
            $rankList = self::parseCoefficient($rankList);
197
            $rankIter = 1;
198
            $rankValue = 1;
199
            $rankSolved = -1;
200
            $rankListCached = [];
201
            self::procRankingPer($totUsers);
202
            foreach ($rankList as $rankItem) {
203
                if ($rankSolved != $rankItem["points"]) {
204
                    $rankValue = $rankIter;
205
                    $rankSolved = $rankItem["points"];
206
                }
207
                $rankTitle = self::getRankTitle($rankValue);
208
                Cache::tags(['rank', $rankItem["uid"]])->put("rank", $rankValue, 86400);
209
                Cache::tags(['rank', $rankItem["uid"]])->put("title", $rankTitle, 86400);
210
                $rankListCached[] = [
211
                    "uid" => $rankItem["uid"],
212
                    "rank" => $rankValue,
213
                    "title" => $rankTitle,
214
                    "titleColor" => self::getColor($rankTitle),
215
                    "solved" => $rankItem["solved"],
216
                    "community" => $rankItem["community"],
217
                    "activityCoefficient" => $rankItem["activityCoefficient"],
218
                ];
219
                $rankIter++;
220
            }
221
            Cache::tags(['rank'])->put("general", $rankListCached, 86400);
222
            $currentRankList = self::list(100);
223
            self::sendRankUpDownMessage($originalRankList, $currentRankList);
224
        }
225
    }
226
227
    private static function sendRankUpDownMessage($originalRankList, $currentRankList)
228
    {
229
        if(blank($originalRankList) || blank($currentRankList)) {
230
            return;
231
        }
232
233
        $originalRankUID = [];
234
        foreach($originalRankList as $originalRankItem) {
235
            $originalRankUID[] = $originalRankItem['uid'];
236
        }
237
238
        $currentRankUID = [];
239
        foreach($currentRankList as $currentRankItem) {
240
            $currentRankUID[] = $currentRankItem['uid'];
241
        }
242
243
        foreach($originalRankList as $originalRankItem) {
244
            if(in_array($originalRankItem['uid'], $currentRankUID)) {
245
                continue;
246
            }
247
            self::sendMessage($originalRankItem['uid'], Cache::tags(['rank', $originalRankItem['uid']])->get("rank", null), $originalRankItem['rank']);
248
        }
249
250
        foreach($currentRankList as $currentRankItem) {
251
            if(in_array($currentRankItem['uid'], $originalRankUID)) {
252
                continue;
253
            }
254
            self::sendMessage($currentRankItem['uid'], $currentRankItem['rank'], null);
255
        }
256
    }
257
258
    public static function getProfessionalRanking()
259
    {
260
        $professionalRankList = [];
261
        $verifiedUsers = User::all();
262
        $rankIter = 0;
263
        foreach ($verifiedUsers as $user) {
264
            $rankVal = $user->professional_rate;
265
            $rankTitle = self::getProfessionalTitle($rankVal);
266
            $titleColor = self::getProfessionalColor($rankTitle);
267
            $professionalRankList[$rankIter++] = [
268
                "name" => $user->name,
269
                "uid" => $user->id,
270
                "avatar" => $user->avatar,
271
                "professionalRate" => $user->professional_rate,
272
                "rankTitle" => $rankTitle,
273
                "titleColor" => $titleColor
274
            ];
275
        }
276
        return $professionalRankList;
277
    }
278
279
    private static function procRankingPer($totUsers)
280
    {
281
        if ($totUsers > 0) {
282
            $tot = 0;
283
            $cur = 0;
284
            foreach (self::$casualRankingPer as $c) {
285
                $tot += $c;
286
            }
287
            foreach (self::$casualRankingPer as &$c) {
288
                $c = round($c * $totUsers / $tot);
289
                $cur += $c;
290
                $c = $cur;
291
            }
292
            $c = $totUsers;
293
            unset($c);
294
        }
295
    }
296
297
    public static function getRankTitle($rankVal)
298
    {
299
        foreach (self::$casualRankingPer as $title => $c) {
300
            if ($rankVal <= $c) {
301
                return $title;
302
            }
303
        }
304
        return Arr::last(self::$casualRankingPer);
305
    }
306
307
    public static function getProfessionalTitle($rankVal)
308
    {
309
        foreach (self::$professionalRankingPer as $title => $point) {
310
            if ($rankVal >= $point) {
311
                return $title;
312
            }
313
        }
314
        return Arr::last(self::$professionalRankingPer);
315
    }
316
}
317