🏡 index : cold-air-networks/mirror-jetstream.git

/*
 * Copyright (C) 2022 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */

"use strict";

function createLists(store, count) {
    for (let i = 0; i < count; i++) {
        store.lists.push(new ObservableReminderList(store, listName(i)));
    }
}

function createReminders(store, count) {
    store.lists.forEach((list, listIndex) => {
        for (let i = 0; i < count; i++) {
            const text = nouns[((listIndex * 10) + i) % nouns.length];
            list.reminders.push(new ObservableReminder(list, text));
        }
    });
}

function randomItem(array) {
    return array[array.length * Math.random() | 0];
}

function listName(index) {
    const date = new Date();
    date.setDate(date.getDate() + index);
    return date.toDateString();
}

const nouns = ["magazine", "passion", "statement", "extent", "judgment", "setting", "piano", "control", "employer", "speech", "presence", "indication", "philosophy", "worker", "supermarket", "atmosphere", "actor", "chest", "garbage", "feedback", "pizza", "speaker", "selection", "effort", "math", "gate", "profession", "recognition", "software", "coffee", "reading", "hotel", "concept", "poem", "population", "property", "office", "phone", "suggestion", "theory", "drawer", "death", "cigarette", "alcohol", "marketing", "session", "cheek", "establishment", "family", "revenue", "location", "mode", "awareness", "preference", "consequence", "cookie", "loss", "poetry", "cabinet", "difference", "story", "penalty", "king", "opinion", "climate", "imagination", "criticism", "meal", "desk", "dealer", "introduction", "physics", "customer", "child", "problem", "medicine", "church", "unit", "tongue", "communication", "storage", "stranger", "presentation", "discussion", "excitement", "role", "ladder", "estate", "town", "proposal", "employee", "administration", "variety", "television", "president", "inspector", "negotiation", "height", "comparison", "decision"];
const adjectives = ["fast", "cheerful", "hungry", "fantastic", "squealing", "critical", "yielding", "abashed", "shocking", "quickest"];

function addRandomTags(store) {
    forEachReminder(store, reminder => {
        if (Math.random() < 0.5) {
            reminder.tags.add(randomItem(adjectives));
        }

        if (Math.random() < 0.5) {
            reminder.tags.add(randomItem(adjectives));                
        }
    });
}

function completeSomeReminders(store) {
    forEachReminder(store, reminder => {
        reminder.isCompleted = Math.random() < 0.5;
    });
}

function sortRemindersByCompleted(store) {
    store.lists.forEach(list => {
        list.reminders.sort((a, b) => b.isCompleted - a.isCompleted);
    });
}

function pushBackRemindersWithTag(store, tag) {
    for (let i = store.lists.length - 2; i >= 0; i--) {
        const list = store.lists[i];
        const nextList = store.lists[i + 1];

        for (let j = list.reminders.length - 1; j >= 0; j--) {
            const reminder = list.reminders[j];
            if (reminder.tags.has(tag)) {
                reminder.moveToAnotherList(nextList);
            }
        }
    }
}

function removeRemindersWithTag(store, tag) {
    store.lists.forEach(list => {
        list.reminders = list.reminders.filter(reminder => !reminder.tags.has(tag));
    });
}

function removeCompletedReminders(store) {
    store.lists.forEach(list => {
        list.reminders = list.reminders.filter(reminder => !reminder.isCompleted);
    });
}

function removeFirstAndLastRemindersFromEachList(store) {
    store.lists.forEach(list => {
        list.reminders.shift();
        list.reminders.pop();
    });
}

function removeEmptyLists(store) {
    for (let i = store.lists.length - 1; i >= 0; i--) {
        const list = store.lists[i];
        if (list.isEmpty) {
            store.lists.splice(i, 1);
        }
    }
}

function forEachReminder(store, callback) {
    store.lists.forEach(list => {
        list.reminders.forEach(callback);
    });
}

function renderTags(entries) {
    return entries.map(([tag, count]) => `#${tag} (${count})`).join(", ") || "<none>";
}

class ReminderStore {
    constructor() {
        this.lists = [];
        this.allTimeCompletedTags = new Map;
    }

    get allTags() {
        const map = new Map;

        forEachReminder(this, reminder => {
            reminder.tags.forEach(tag => {
                const oldCount = map.get(tag) ?? 0;
                map.set(tag, oldCount + 1);
            });
        });

        return map;
    }

    get completedRemidersCount() {
        return this.lists.reduce((count, list) => count + list.completedRemidersCount, 0);
    }

    get remindersCount() {
        return this.lists.reduce((count, list) => count + list.reminders.length, 0);
    }

    get popularTags() {
        return this.popularTagsWithCount.map(e => e[0]);
    }

    get popularTagsWithCount() {
        return [...this.allTags].sort((a, b) => b[1] - a[1]).slice(0, 5);
    }

    render() {
        return [
            this.lists.map(list => list.render()).join("\n") || "<empty>",
            "-".repeat(31),
            `completed: ${this.completedRemidersCount} / ${this.remindersCount}`,
            "-".repeat(31),
            `current popular tags: ${renderTags(this.popularTagsWithCount)}`,
            `all-time completed tags: ${renderTags([...this.allTimeCompletedTags])}`,
        ].join("\n");
    }
}

class ReminderList {
    constructor(store, name) {
        this._store = store;

        this.name = name;
        this.reminders = [];
    }

    get store() {
        return this._store;
    }

    get indexInStore() {
        return this._store.lists.indexOf(this);
    }

    get isEmpty() {
        return !this.reminders.length;
    }

    get completedRemidersCount() {
        return this.reminders.filter(reminder => reminder.isCompleted).length;
    }

    render() {
        const content = this.isEmpty ? "<empty>" : this.reminders.map(reminder => reminder.render()).join("\n");

        return `--- ${this.name} (${this.completedRemidersCount} / ${this.reminders.length}) ---\n${content}`;
    }
}

class Reminder {
    constructor(list, text) {
        this._list = list;

        this.text = text;
        this.tags = new Set();
        this.isCompleted = false;
    }

    get list() {
        return this._list;
    }

    get indexInList() {
        return this._list.reminders.indexOf(this);
    }

    _isCompletedChanged(isCompleted) {
        if (isCompleted) {
            const tagsMap = this._list.store.allTimeCompletedTags;
            this.tags.forEach(tag => {
                const oldCount = tagsMap.get(tag) ?? 0;
                tagsMap.set(tag, oldCount + 1);
            });
        }
    }

    moveToAnotherList(newList) {
        this._list.reminders.splice(this.indexInList, 1);
        this._list = newList;
        newList.reminders.unshift(this);
    }

    render() {
        const tags = this.tags.size ? ` (${Array.from(this.tags, tag => `#${tag}`).join(" ")})` : "";

        return `[${this.isCompleted ? "x" : " "}] ${this.text}${tags}`;
    }
}

async function test(store, render) {
    await render(() => createLists(store, 10));
    await render(() => createReminders(store, 10));
    await render(() => addRandomTags(store));

    const { popularTags } = store;
    await render(() => pushBackRemindersWithTag(store, popularTags[0]));
    await render(() => removeRemindersWithTag(store, popularTags[2]));

    await render(() => removeFirstAndLastRemindersFromEachList(store));
    await render(() => completeSomeReminders(store));
    await render(() => sortRemindersByCompleted(store));

    await render(() => removeCompletedReminders(store));
    await render(() => removeEmptyLists(store));
}