root/TvShows.c

#include <curl/curl.h>
#include <ncurses.h>
#include <string.h>
#include "json.h"
#include "TvShows.h"
#include "CurlResult.h"
#include "Colors.h"
#include "EpisodeList.h"
#include "EpisodePage.h"
#include "utils.h"

void TvShows_init(struct TvShows* this) {
    this->lists = List_create();
    this->selectedList = NULL;
    this->scrollY = 0;
    Search_init(&this->search);
    int x, y;
    getmaxyx(stdscr, y,x);
    this->window = newwin(y-PLAYER_H, x, 0, 0);
    scrollok(this->window, true);
}

void TvShows_destroyMembers(struct TvShows* this) {
    Search_destroyMembers(&this->search);
    List_destroyWithContent(this->lists, TvShowList_destroy);
}

DEFAULT_CREATE_DESTROY(TvShows)

bool TvShows_handleInput(struct TvShows* this, int c) {
    if ((this != *this->activePage && c != KEY_RESIZE)) {
        return false;
    }

    if (Search_handleInput(&this->search, c)) {
        Search_draw(&this->search, this->window);
        if (this->search.status == searchBrowse) {
            TvShows_searchForward(this);
        }
        return true;
    }

    if (c == 'n' || c == 'j' || c == KEY_DOWN) {
        if (this->search.status == searchBrowse) {
            TvShows_searchForward(this);
        } else {
            TvShows_selectDelta(this, +1);
        }
    } else if (c == 'p' || c == 'k' || c == KEY_UP) {
        if (this->search.status == searchBrowse) {
            TvShows_searchBackward(this);
        } else {
            TvShows_selectDelta(this, -1);
        }
    } else if (c == PAGE_UP) {
        TvShows_selectDelta(this, -10);
    } else if (c == PAGE_DOWN) {
        TvShows_selectDelta(this, +10);
    } else if (c == 'f' || c == 'l' || c == KEY_RIGHT) {
        struct TvShowLi* tvShow = TvShows_selectedTvShow(this);
        if (tvShow) {
            *this->activePage = this->episodePage;
            safe_free(this->episodePage->name);
            this->episodePage->name = tvShow->name
                ? copy_string(tvShow->name, strlen(tvShow->name))
                : NULL;
            EpisodePage_fetch(this->episodePage, tvShow->name);
            EpisodePage_activate(this->episodePage);
        }
    } else if (c == KEY_HOME || c == '<') {
        TvShows_selectFirstOrLast(this, true);
    } else if (c == KEY_END || c == '>') {
        TvShows_selectFirstOrLast(this, false);
    } else if (c == '/') {
        if (searchRead != this->search.status) {
            Search_clear(&this->search);
        }
        this->search.status = searchRead;
    } else if (c == 'g') {
        this->search.status = searchInactive;
        Search_clear(&this->search);
    } else if (c == '\n' || c == 'a') {
        TvShows_playSelected(this);
    } else if (c == 'u') {
        TvShows_fetch(this, this->baseUrl);
    } else if (c == KEY_RESIZE) {
        int screenX, screenY;
        getmaxyx(stdscr, screenY, screenX);
        wresize(this->window, screenY-PLAYER_H, screenX);
        wclear(this->window);
        TvShows_printAll(this, NULL);
        return false;
    } else {
        return false;
    }
    return true;
}

void TvShows_playSelected(struct TvShows* this) {
    struct TvShowLi* tvShow = TvShows_selectedTvShow(this);
    if (!tvShow) {
        return;
    }
    const char* name = tvShow->name;
    struct EpisodeList* episodeList = EpisodeList_fetch(this->baseUrl, name);
    EpisodeList_play(episodeList, this->baseUrl, EpisodeList_contstructPlaylist(episodeList));
    EpisodeList_destroy(episodeList);
}

struct TvShowLi* TvShows_selectedTvShow(struct TvShows* this) {
    if (!this->selectedList) return NULL;
    struct TvShowList* list = this->selectedList->data;
    if (!list->selectedLi) return NULL;
    return list->selectedLi->data;
}

void TvShows_searchInDirection(struct TvShows* this, bool direction) {
    if (!this->search.queryUtf8) {
        return;
    }

    ListNode* listingNode = this->selectedList
        ? this->selectedList
        : (direction ? this->lists->first : this->lists->last);
    while (listingNode) {
        struct TvShowList* listing = listingNode->data;
        ListNode* liNode = direction
            ? (listing->selectedLi
               ? listing->selectedLi->next
               : listing->lis->first)
            : (listing->selectedLi
               ? listing->selectedLi->previous
               : listing->lis->last);
        while (liNode) {
            struct TvShowLi* li = liNode->data;
            const char* match = strstr(li->name, this->search.queryUtf8);
            if (match) {
                if (this->selectedList) {
                    struct TvShowList* listing = this->selectedList->data;
                    listing->selectedLi = NULL;
                }
                this->selectedList = listingNode;
                listing->selectedLi = liNode;
                TvShows_selectDelta(this, 0);
                return;
            }
            liNode = direction ? liNode->next : liNode->previous;
        }
        listingNode = direction ? listingNode->next : listingNode->previous;
    }
}
void TvShows_searchForward(struct TvShows* this) {
    TvShows_searchInDirection(this, true);
}

void TvShows_searchBackward(struct TvShows* this) {
    TvShows_searchInDirection(this, false);
}

void TvShows_selectFirstOrLast(struct TvShows* this, bool direction) {
    this->selectedList = direction
        ? this->lists->first
        : this->lists->last;
    if (this->selectedList) {
        struct TvShowList* list = this->selectedList->data;
        list->selectedLi = direction
            ? list->lis->first
            : list->lis->last;
    }
    TvShows_printAll(this, NULL);
    if (direction) {
        TvShows_selectDelta(this, 1);
        TvShows_selectDelta(this, -1);
    } else {
        TvShows_selectDelta(this, -1);
        TvShows_selectDelta(this, 1);
    }
}

void TvShows_selectDelta(struct TvShows* this, int delta) {

    if (!this->selectedList) {
        this->selectedList = delta >= 0
            ? this->lists->first
            : this->lists->last;
        if (!this->selectedList) {
            return;
        }
    }

    struct TvShowList* list = this->selectedList->data;
    delta = TvShowList_selectDelta(list, this->window, delta);
    while (delta != 0 && this->selectedList) {
        if (delta > 0) {
            struct TvShowList* listing = this->selectedList->data;
            listing->selectedLi = NULL;
            this->selectedList = this->selectedList->next;
        } else if (delta < 0) {
            struct TvShowList* listing = this->selectedList->data;
            listing->selectedLi = NULL;
            this->selectedList = this->selectedList->previous;
        }
        if (this->selectedList) {
            list = this->selectedList->data;
            delta = TvShowList_selectDelta(list, this->window, delta);
        }
    }

    bool didDraw = false;
    if (this->selectedList) {
        struct TvShowList* list = this->selectedList->data;
        if (list->selectedLi) {
            struct TvShowLi* li = list->selectedLi->data;
            int screenY = getmaxy(stdscr);
            if (li->y > 1) {
                int offset = (screenY-PLAYER_H)/2;
                int newScroll = li->y - offset;
                newScroll = newScroll > 0 ? newScroll : 0;
                int diff = newScroll - this->scrollY;

                /* wscrl(this->window, diff); */
                wclear(this->window);
                if (diff != 0) {
                    int linesToUpdate[4] = { this->scrollY,
                                             this->scrollY + (screenY-1),
                                             li->y, -1};
                    this->scrollY = newScroll;
                    TvShows_printAll(this, NULL);
                    didDraw = true;
                }
            }
        }
    }
    if (!didDraw) {
        TvShows_printAll(this, NULL);
    }
}

int TvShows_printAll(struct TvShows* this, int* linesToUpdate) {
    if (this != *this->activePage) {
        return -1;
    }

    int y = 0;
    for (ListNode* it = this->lists->first; it; it = it->next) {
        struct TvShowList* data = it->data;
        if (data) {
            bool noMoreLinesToDraw = (NULL != linesToUpdate && -1 == *linesToUpdate);
            WINDOW* drawWindow = noMoreLinesToDraw
                ? NULL
                : this->window;
            struct DrawPair info = {
                .y = y > 0 ? (y+1) : y, /* space between lists */
                .linesToUpdate = linesToUpdate
            };
            struct DrawPair result = TvShowList_draw(data, drawWindow, this->scrollY, info);
            y = result.y;
            linesToUpdate = result.linesToUpdate;
        }
    }
    if (this->window) {
        wrefresh(this->window);
    }
    return y;
}
void TvShows_fetch(struct TvShows* this, const char* baseUrl) {
    CURL* handle = curl_easy_init();
    struct CurlResult userdata;
    CurlResult_init(&userdata);
    char url[256];
    sprintf(url, "%s/%s", baseUrl, "api/library/filter/lists");
    curl_easy_setopt(handle, CURLOPT_URL, url);
    curl_easy_setopt(handle, CURLOPT_TIMEOUT, 15);
    curl_easy_setopt(handle, CURLOPT_NOSIGNAL, 1);
    curl_easy_setopt(handle, CURLOPT_WRITEDATA, &userdata);
    curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, CurlResult_write);
    curl_easy_perform(handle);
    json_value* json = CurlResult_parse(&userdata);
    TvShows_restore(this, json);

    TvShows_printAll(this, NULL);
    wrefresh(this->window);
    this->baseUrl = baseUrl;

    json_value_free(json);
    CurlResult_destroyMembers(&userdata);
    curl_easy_cleanup(handle);
}

void TvShows_restore(struct TvShows* this, json_value* json) {
    if (!json || json->type != json_object) {
        printw("failed to parse json: %p\n", json);
        refresh();
        return;
    }
    int length = json->u.object.length;
    for (int i=0; i < length; ++i) {
        char* itemName = json->u.object.values[i].name;
        if (0 == strcmp(itemName, "lists")) {
            json_value* lists = json->u.object.values[i].value;
            if (lists->type != json_object) {
                continue;
            }
            int listsLength = lists->u.object.length;
            for (int j=0; j < listsLength; ++j) {
                char* name = lists->u.object.values[j].name;
                json_value* list = lists->u.object.values[j].value;
                TvShows_restoreList(this, name, list);
            }
        }
    }
}

void TvShows_restoreList(struct TvShows* this, const char* name, json_value* json) {
    if (json->type != json_array) {
        return;
    }
    struct TvShowList* list = TvShowList_create();
    bool hasLis = false;
    int length = json->u.array.length;
    for (int i=0; i < length; ++i) {
        json_value* value = json->u.array.values[i];
        struct TvShowLi* li = TvShowLi_restore(value);
        if (li) {
            hasLis = true;
            List_pushBack(list->lis, li);
        }
    }
    if (hasLis) {
        list->name = copy_string(name, strlen(name));
        List_qSort(list->lis, TvShowLi_compare);
        List_pushBack(this->lists, list);
    } else {
        TvShowList_destroy(list);
    }
}

struct TvShowLi* TvShowLi_restore(json_value* json) {
    if (json->type != json_object) {
        return NULL;
    }
    struct TvShowLi* li = malloc(sizeof(struct TvShowLi));
    int showLength = json->u.object.length;
    for (int i=0; i < showLength; ++i) {
        char* key = json->u.object.values[i].name;
        json_value* value = json->u.object.values[i].value;
        if (0 == strcmp(key, "name") && value->type == json_string) {
            li->name = copy_string(value->u.string.ptr, value->u.string.length);
        } else if (0 == strcmp(key, "downloadedEpisodes") && value->type == json_integer) {
            li->downloadedEpisodes = value->u.integer;
        } else if (0 == strcmp(key, "totalEpisodes") && value->type == json_integer) {
            li->totalEpisodes = value->u.integer;
        } else if (0 == strcmp(key, "watchedEpisodes") && value->type == json_integer) {
            li->watchedEpisodes = value->u.integer;
        }
    }
    return li;
}


void TvShowList_init(struct TvShowList* this) {
    this->name = NULL;
    this->lis = List_create();
    this->selectedLi = NULL;
}

void TvShowList_destroyMembers(struct TvShowList* this) {
    safe_free(this->name);
    List_destroyWithContent(this->lis, TvShowLi_destroy);
}

DEFAULT_CREATE_DESTROY(TvShowList)

bool shouldDrawLine(struct DrawPair info, int y) {
    return (NULL == info.linesToUpdate || *info.linesToUpdate == y);
}

struct DrawPair TvShowList_draw(struct TvShowList* this, WINDOW* window, int scrollY, struct DrawPair info) {
    int y = info.y;
    int hfy = y-scrollY; /* header-final-y */
    int screenY = getmaxy(stdscr);
    if (window && hfy > -1 && hfy < (screenY) && shouldDrawLine(info, y)) {
        wmove(window, hfy, 0);
        wattron(window, A_UNDERLINE);
        wprintw(window, "list %s", this->name); /*ウニコード モタファクぁ*/
        wattroff(window, A_UNDERLINE);
        if (info.linesToUpdate) {
            ++info.linesToUpdate;
        }
    }

    ++y;
    for (ListNode* it = this->lis->first; it; it = it->next) {
        struct TvShowLi* data = it->data;
        if (data) {
            int fy = y - (scrollY); /* final-y */
            if (window && y > 0 && fy < (screenY) && fy >= 0 && shouldDrawLine(info, y)) {
                wmove(window, fy, 0);
                wprintw(window, "%d", y);
                /* wmove(window, fy > 0 ? fy : 1, 3); */
                /* waddstr(window, data->name); */
                bool selected = this->selectedLi && this->selectedLi->data == data;
                TvShowLi_draw(data, window, selected, fy);
                if (info.linesToUpdate) {
                    ++info.linesToUpdate;
                }
            }
            data->y = y;
            ++y;
        }
    }
    struct DrawPair result = {
        .y = y,
        .linesToUpdate = info.linesToUpdate
    };
    return result;
}

int TvShowList_selectDelta(struct TvShowList* this, WINDOW* window, int delta) {
    /* ListNode* previous = this->selectedLi; */
    if (!this->selectedLi) {
        if (delta > 0) {
            this->selectedLi = this->lis->first;
            if (this->selectedLi) { --delta; }
        } else if (delta < 0) {
            this->selectedLi = this->lis->last;
            if (this->selectedLi) { ++delta; }
        }
    }

    while (delta > 0 && this->selectedLi) {
        this->selectedLi = this->selectedLi->next;
        if (this->selectedLi) { --delta; }
    }
    while (delta < 0 && this->selectedLi) {
        this->selectedLi = this->selectedLi->previous;
        if (this->selectedLi) { ++delta; }
    }

    return delta;
}

void TvShowLi_init(struct TvShowLi* this) {
    this->name = NULL;
    this->watchedEpisodes = 0;
    this->downloadedEpisodes = 0;
    this->totalEpisodes = 0;
    this->y = 0;
}

void TvShowLi_destroyMembers(struct TvShowLi* this) {
    safe_free(this->name);
}

DEFAULT_CREATE_DESTROY(TvShowLi)



void TvShowLi_draw(struct TvShowLi* li, WINDOW* window, bool selected, int y) {
    wmove(window, y, 3);
    if (selected) {
        Colors_wset(window, Colors_Selected);
    }
    int x = getmaxx(window);
    int clearChars = x - safe_strlen(li->name) - 20;
    /* wscrl(window, -y); */
    waddstr(window, li->name);
    for (int i=0; i < clearChars; ++i) {
        waddch(window, ' ');
    }
    wprintw(window, "  | %d/%d/%d", li->watchedEpisodes,
            li->downloadedEpisodes, li->totalEpisodes);
    if (selected) {
        Colors_wset(window, Colors_Default);
    }
    wmove(window, y, 0);
    wrefresh(window);
}

int TvShowLi_compare(void* av, void* bv) {
    struct TvShowLi* a = av;
    struct TvShowLi* b = bv;
    if (!a->name && !b->name) {
        return 0;
    }
    if (!a->name) {
        return -1;
    }
    if (!b->name) {
        return 1;
    }
    int result = strcmp(a->name, b->name);
    return result < 0 ? -1 :
        result > 0 ? 1 :
        0;
}