1#include "database.h"
2#include <algorithm>
3#include <numeric>
4#include <fstream>
5#include <iostream>
6#include <iomanip>
7
8/**
9 * @brief Add a new student to the database
10 */
11void StudentDatabase::addStudent(std::shared_ptr<Student> student) {
12 // Check for duplicate ID
13 auto it = std::find_if(students.begin(), students.end(),
14 [&student](const std::shared_ptr<Student>& s) {
15 return s->getId() == student->getId();
16 });
17
18 if (it != students.end()) {
19 throw std::invalid_argument("Student with ID " + std::to_string(student->getId()) + " already exists");
20 }
21
22 students.push_back(student);
23}
24
25/**
26 * @brief Remove a student by ID
27 */
28void StudentDatabase::removeStudent(int id) {
29 auto it = std::find_if(students.begin(), students.end(),
30 [id](const std::shared_ptr<Student>& s) {
31 return s->getId() == id;
32 });
33
34 if (it == students.end()) {
35 throw StudentNotFoundException(id);
36 }
37
38 students.erase(it);
39}
40
41/**
42 * @brief Find a student by ID
43 * @return Shared pointer to student
44 * @throws StudentNotFoundException if not found
45 */
46std::shared_ptr<Student> StudentDatabase::findById(int id) const {
47 auto it = std::find_if(students.begin(), students.end(),
48 [id](const std::shared_ptr<Student>& s) {
49 return s->getId() == id;
50 });
51
52 if (it == students.end()) {
53 throw StudentNotFoundException(id);
54 }
55
56 return *it;
57}
58
59/**
60 * @brief Find all students with matching name (case-sensitive substring)
61 */
62std::vector<std::shared_ptr<Student>> StudentDatabase::findByName(const std::string& name) const {
63 std::vector<std::shared_ptr<Student>> results;
64
65 std::copy_if(students.begin(), students.end(), std::back_inserter(results),
66 [&name](const std::shared_ptr<Student>& s) {
67 return s->getName().find(name) != std::string::npos;
68 });
69
70 return results;
71}
72
73/**
74 * @brief Display all students
75 */
76void StudentDatabase::listAll() const {
77 if (students.empty()) {
78 std::cout << "No students in database.\n";
79 return;
80 }
81
82 std::cout << "\n" << std::string(80, '=') << "\n";
83 std::cout << "STUDENT DATABASE (" << students.size() << " students)\n";
84 std::cout << std::string(80, '=') << "\n";
85
86 for (const auto& student : students) {
87 std::cout << *student << "\n";
88 }
89
90 std::cout << std::string(80, '=') << "\n\n";
91}
92
93/**
94 * @brief Sort students by ID (ascending)
95 */
96void StudentDatabase::sortById() {
97 std::sort(students.begin(), students.end(),
98 [](const std::shared_ptr<Student>& a, const std::shared_ptr<Student>& b) {
99 return a->getId() < b->getId();
100 });
101}
102
103/**
104 * @brief Sort students by name (alphabetical)
105 */
106void StudentDatabase::sortByName() {
107 std::sort(students.begin(), students.end(),
108 [](const std::shared_ptr<Student>& a, const std::shared_ptr<Student>& b) {
109 return a->getName() < b->getName();
110 });
111}
112
113/**
114 * @brief Sort students by GPA (descending - highest first)
115 */
116void StudentDatabase::sortByGpa() {
117 std::sort(students.begin(), students.end(),
118 [](const std::shared_ptr<Student>& a, const std::shared_ptr<Student>& b) {
119 return a->getGpa() > b->getGpa();
120 });
121}
122
123/**
124 * @brief Calculate average GPA of all students
125 */
126double StudentDatabase::calculateAverageGpa() const {
127 if (students.empty()) {
128 return 0.0;
129 }
130
131 double sum = std::accumulate(students.begin(), students.end(), 0.0,
132 [](double total, const std::shared_ptr<Student>& s) {
133 return total + s->getGpa();
134 });
135
136 return sum / students.size();
137}
138
139/**
140 * @brief Count students by major
141 * @return Map of major -> student count
142 */
143std::map<std::string, int> StudentDatabase::countByMajor() const {
144 std::map<std::string, int> majorCount;
145
146 for (const auto& student : students) {
147 majorCount[student->getMajor()]++;
148 }
149
150 return majorCount;
151}
152
153/**
154 * @brief Save database to CSV file
155 */
156void StudentDatabase::saveToFile(const std::string& filename) const {
157 std::ofstream file(filename);
158
159 if (!file.is_open()) {
160 throw std::runtime_error("Failed to open file for writing: " + filename);
161 }
162
163 // Write header
164 file << "id,name,major,gpa\n";
165
166 // Write student data
167 for (const auto& student : students) {
168 file << student->toCSV() << "\n";
169 }
170
171 file.close();
172 std::cout << "Database saved to " << filename << " (" << students.size() << " students)\n";
173}
174
175/**
176 * @brief Load database from CSV file
177 */
178void StudentDatabase::loadFromFile(const std::string& filename) {
179 std::ifstream file(filename);
180
181 if (!file.is_open()) {
182 throw std::runtime_error("Failed to open file for reading: " + filename);
183 }
184
185 students.clear();
186 std::string line;
187
188 // Skip header
189 std::getline(file, line);
190
191 // Read student data
192 int count = 0;
193 while (std::getline(file, line)) {
194 if (line.empty()) continue;
195
196 try {
197 auto student = std::make_shared<Student>(Student::fromCSV(line));
198 students.push_back(student);
199 count++;
200 } catch (const std::exception& e) {
201 std::cerr << "Warning: Failed to parse line: " << line << " (" << e.what() << ")\n";
202 }
203 }
204
205 file.close();
206 std::cout << "Database loaded from " << filename << " (" << count << " students)\n";
207}