#include <cassert>
#include <vector>

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */

struct ListNode {
    int val;
    ListNode *next;
    ListNode() : val(0), next(nullptr) {}
    ListNode(int x) : val(x), next(nullptr) {}
    ListNode(int x, ListNode *next) : val(x), next(next) {}
};

namespace {

struct middle_t {
    ListNode *node;
    std::size_t size;

    middle_t(ListNode *node, std::size_t size) : node(node), size(size) {}
};

middle_t find_middle(ListNode *head) {
    ListNode *slow = head;
    ListNode *fast = head->next;

    std::size_t size;
    for (size = 0; fast != nullptr && fast->next != nullptr; size++) {
        slow = slow->next;
        fast = fast->next->next;
    }

    return {slow, size};
}

ListNode *reverse_list(ListNode *tail) {
    ListNode *previous = nullptr;
    ListNode *next = nullptr;

    for (ListNode *current = tail; current != nullptr;
         previous = current, current = next) {
        next = current->next;
        current->next = previous;
    }

    return previous;
}

bool compare_lists(ListNode *left, ListNode *right, std::size_t size) {
    for (auto i = 0u; i < size + 1; i++) {
        if (left->val != right->val) {
            return false;
        }
        left = left->next;
        right = right->next;
    }

    return true;
}

} // namespace

class Solution {
  public:
    bool isPalindrome(ListNode *head) {
        // find middle of the linked list
        auto mid = find_middle(head);

        // reverse the tail of the linked list
        auto tail = reverse_list(mid.node);

        // compare head and tail
        return compare_lists(head, tail, mid.size);
    }
};

#pragma region Testing utilities
namespace {

ListNode *construct_list(const std::vector<int> &values) {
    ListNode *head = new ListNode(values.front());
    ListNode *tail = head;

    for (std::size_t i = 1; i < values.size(); i++) {
        tail->next = new ListNode(values[i]);
        tail = tail->next;
    }

    return head;
}

void destroy_list(ListNode *linked_list) {
    if (linked_list == nullptr) {
        return;
    }
    destroy_list(linked_list->next);
    delete linked_list;
}

bool test_find_middle(const std::vector<int> &values, int mid_value) {
    auto linked_list = construct_list(values);

    auto mid = find_middle(linked_list);
    auto result = mid.node->val == mid_value;

    destroy_list(linked_list);
    return result;
}

bool test_isPalindrome(const std::vector<int> &values) {
    auto linked_list = construct_list(values);

    Solution s;
    auto result = s.isPalindrome(linked_list);

    destroy_list(linked_list);
    return result;
}

} // namespace
#pragma endregion // Testing utilities

int main() {
    // find_middle tests
    assert(test_find_middle(std::vector{1, 2, 2, 1}, 2));
    assert(test_find_middle(std::vector{1, 2, 3, 2, 1}, 3));
    assert(test_find_middle(std::vector{1, 2}, 1));
    assert(test_find_middle(std::vector{1}, 1));

    // isPalindrome tests
    assert(test_isPalindrome(std::vector{1, 2, 2, 1}));
    assert(test_isPalindrome(std::vector{1, 2, 3, 2, 1}));
    assert(!test_isPalindrome(std::vector{1, 2}));
    assert(test_isPalindrome(std::vector{1}));

    return 0;
}