import sys
from PyQt5.QtWidgets import *  # PyQt5에서 다양한 위젯들을 가져옵니다. 버튼, 레이아웃 등
from PyQt5.QtGui import *  # PyQt5에서 그래픽 관련 클래스를 가져옵니다. 색상, 폰트 등
from PyQt5.QtCore import QDate, Qt  # PyQt5에서 날짜와 정렬 관련 클래스를 가져옵니다.
from PyQt5 import uic  # UI 파일을 로드하는 기능을 제공합니다.
import cx_Oracle as oci  # Oracle 데이터베이스와 연결하는 라이브러리입니다.
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas  # matplotlib 그래프를 Qt에 넣기 위한 캔버스를 생성하는 라이브러리입니다.
from matplotlib.figure import Figure  # matplotlib에서 그래프를 그릴 수 있는 Figure 객체를 생성합니다.
import matplotlib.font_manager as fm  # 한글 폰트를 처리하기 위한 라이브러리입니다.
import numpy as np  # 수학적인 계산을 쉽게 할 수 있도록 도와주는 라이브러리입니다.

# 데이터베이스 연결 정보 (Oracle 데이터베이스에 연결)
sid = 'XE'
host = '127.0.0.1'
port = 1521
username = 'attendance'
password = '12345'

class AttendanceGraph(QWidget):  # AttendanceGraph는 출석 비율을 그래프 형식으로 표시하는 클래스입니다.
    def __init__(self, parent=None):
        super().__init__(parent)
        self.figure = Figure()  # Figure 객체를 만들어서 그래프를 그릴 준비를 합니다.
        self.canvas = FigureCanvas(self.figure)  # 이 캔버스 위에 그래프를 그립니다.
        layout = QVBoxLayout(self)  # 수직 레이아웃을 만들어서 그래프를 위젯에 넣습니다.
        layout.addWidget(self.canvas)  # 캔버스를 레이아웃에 추가합니다. 

        font_path = "C:/Windows/Fonts/malgun.ttf"  # 사용할 한글 폰트 파일 경로입니다.
        self.font_prop = self.set_korean_font(font_path)  # 한글 폰트를 설정합니다.

    def set_korean_font(self, font_path):  # 한글 폰트를 설정하는 메서드입니다.
        try:
            font = fm.FontProperties(fname=font_path)  # 폰트 파일을 읽어서 FontProperties 객체로 만듭니다.
            return font  # 만든 폰트를 반환합니다.
        except Exception as e:
            print("폰트 로드 오류:", e)  # 폰트를 불러오지 못했을 경우 오류를 출력합니다.
            return None  # 오류가 나면 None을 반환합니다.

    def update_hbar_graph(self, monthly_data):  # 월별 출석률 그래프를 업데이트하는 메서드입니다.
        self.figure.clear()  # 기존의 그래프를 지우고 새로운 그래프를 그리기 위해 초기화합니다.
        ax = self.figure.add_subplot(111)  # (1,1,1)형태로 1개의 서브플롯을 추가합니다. (행 1, 열 1, 위치 1)

        months = [f"{i}월" for i in range(1, 13)]  # "1월", "2월", ..., "12월"로 월별 이름을 만들어 리스트로 저장합니다.
        attendance_rates = [0] * 12  # 12개월에 대한 출석률을 0으로 초기화합니다. (기본 출석률은 0%)

        # 월별 출석 데이터를 순회하며 출석률을 계산합니다.
        for month in range(1, 13):
            if month in monthly_data:  # 해당 월에 데이터가 있으면
                data = monthly_data[month]  # 해당 월의 출석 데이터를 가져옵니다.
                total_o = data.get('O', 0)  # 'O'는 출석을 의미합니다. 데이터가 없으면 0으로 설정합니다.
                total_x = data.get('X', 0)  # 'X'는 결석을 의미합니다. 데이터가 없으면 0으로 설정합니다.
                total_triangles = data.get('△', 0)  # '△'는 지각을 의미합니다. 데이터가 없으면 0으로 설정합니다.

                adjusted_x = total_x + (total_triangles // 3)  # 지각은 3번마다 결석으로 간주합니다.
                total_attendance = total_o + adjusted_x + total_triangles  # 전체 출석 수를 계산합니다.

                if total_attendance > 0:  # 출석자가 1명이라도 있으면 출석률을 계산합니다.
                    attendance_rates[month - 1] = (total_o / total_attendance) * 100  # 출석률을 계산하여 저장합니다.

        y_pos = np.arange(len(months))  # 월별 위치를 y축에 맞게 설정합니다. (0~11)
        colors = ['red' if rate < 66 else 'green' for rate in attendance_rates]  # 출석률이 66% 미만이면 빨간색, 이상이면 초록색으로 표시합니다.

        # 수평 막대 그래프를 그립니다. (출석률을 기준으로 월별 색상과 비율을 표시)
        ax.barh(y_pos, attendance_rates, color=colors, align='center')  
        ax.set_yticks(y_pos)  # y축의 위치를 설정합니다.
        ax.set_yticklabels(months, fontproperties=self.font_prop)  # 월 이름을 y축에 표시합니다.
        ax.set_xlabel("출석 비율 (%)", fontproperties=self.font_prop)  # x축 레이블을 "출석 비율 (%)"로 설정합니다.
        ax.set_xlim(0, 100)  # x축의 범위를 0에서 100까지 설정합니다.
        ax.set_title("월별 출석 비율", fontproperties=self.font_prop)  # 그래프 제목을 설정합니다.

        self.canvas.draw()  # 캔버스에 새로운 그래프를 그립니다.

class CustomCalendar(QCalendarWidget):  # 사용자 정의 달력을 구현하는 클래스입니다.
    def __init__(self, parent=None):
        super().__init__(parent)
        self.symbols = {}  # 날짜별 출석 상태를 저장할 딕셔너리입니다. 예시: {QDate(2025, 3, 29): 'O'}
        self.setVerticalHeaderFormat(QCalendarWidget.NoVerticalHeader)  # 세로 헤더를 없애고 날짜만 표시하도록 설정합니다.
        self.parent = parent
        self.load_attendance_data()  # 출석 데이터를 불러오는 메서드를 호출합니다.

    def paintCell(self, painter, rect, date):  # 각 셀에 출석 상태와 시간을 그리는 메서드입니다.
        super().paintCell(painter, rect, date)  # 기본 셀을 먼저 그립니다.

        if date in self.symbols:  # 해당 날짜에 출석 상태가 있으면
            symbol, time = self.symbols[date]  # 출석 상태와 시간을 가져옵니다.
            color_map = {'O': "blue", '△': "green", 'X': "red"}  # 출석 상태에 따른 색상입니다.

            painter.setPen(QColor(color_map.get(symbol, "black")))  # 출석 상태에 맞는 색상으로 설정합니다.

            font = QFont("Arial", 12, QFont.Bold)  # 텍스트는 굵은 글씨로 설정합니다.
            painter.setFont(font)
            painter.drawText(rect.adjusted(rect.width() // 3, 0, 0, 0), Qt.AlignLeft, symbol)  # 셀의 왼쪽에 출석 상태를 그립니다.

            if symbol != 'X':  # 결석 상태가 아닌 경우에만 시간을 표시합니다.
                painter.setFont(QFont("Arial", 6))  # 시간을 작은 글씨로 표시합니다.
                painter.drawText(rect.adjusted(0, rect.height() // 2, 0, 0), Qt.AlignCenter, time)  # 셀의 중앙에 시간을 표시합니다.

    def load_attendance_data(self):  # 데이터베이스에서 출석 데이터를 불러오는 메서드입니다.
        try:
            conn = oci.connect(f'{username}/{password}@{host}:{port}/{sid}')  # Oracle DB에 연결합니다.
            cursor = conn.cursor()
            query = '''
                SELECT ATD_DATE, STATUS, TO_CHAR(ATD_TIME, 'HH24:MI')
                FROM ATTENDANCE.ATD
                WHERE S_NO = 1
            '''  # 출석 데이터를 가져오는 SQL 쿼리입니다.
            cursor.execute(query)
            rows = cursor.fetchall()  # 모든 출석 데이터를 가져옵니다.

            status_map = {'P': 'O', 'L': '△', 'A': 'X'}  # 출석 상태 코드 변환 (P: 출석, L: 지각, A: 결석)
            monthly_data = {}  # 월별 출석 데이터를 저장할 딕셔너리입니다.
            self.symbols.clear()  # 기존의 출석 상태를 초기화합니다.

            for date, status, time in rows:  # 데이터가 하나씩 순차적으로 처리됩니다.
                qdate = QDate(date.year, date.month, date.day)  # 날짜 객체를 만듭니다.
                month = date.month  # 날짜에서 월을 추출합니다.

                # 시간을 기준으로 출석 상태를 결정합니다.
                hour, minute = map(int, time.split(':'))  # 시간을 시와 분으로 나눕니다.

                if hour < 9:
                    symbol = 'O'  # 오전 9시 이전은 출석으로 처리합니다.
                elif 9 <= hour < 13:
                    symbol = '△'  # 오후 1시 이전은 지각으로 처리합니다.
                else:
                    symbol = 'X'  # 오후 1시 이후는 결석으로 처리합니다.

                self.symbols[qdate] = (symbol, time)  # 해당 날짜에 출석 상태와 시간을 기록합니다.

                if month not in monthly_data:
                    monthly_data[month] = {'O': 0, '△': 0, 'X': 0}  # 월별 출석 데이터를 초기화합니다.
                monthly_data[month][symbol] += 1  # 해당 월의 출석 상태 카운트를 증가시킵니다.

            print("출결 데이터 로드 완료:", self.symbols)

            self.updateCells()  # 달력의 셀을 업데이트합니다.
            self.parent.graph_widget.update_hbar_graph(monthly_data)  # 그래프를 업데이트합니다.

        except Exception as e:
            print("데이터베이스 오류:", e)  # 데이터베이스 오류 처리

        finally:
            cursor.close()  # 커서 닫기
            conn.close()  # 데이터베이스 연결 닫기

class AttendanceApp(QMainWindow):  # 출석 관리 앱의 메인 클래스입니다.
    def __init__(self):
        super().__init__()
        uic.loadUi('./미니프젝/출석관리,통계3.ui', self)  # UI 파일을 로드합니다.

        self.graph_widget = AttendanceGraph(self)  # 그래프 위젯을 생성합니다.

        vertical_layout = self.findChild(QVBoxLayout, "verticalLayout")  # UI에서 수직 레이아웃을 찾습니다.
        if vertical_layout:
            vertical_layout.addWidget(self.graph_widget)  # 그래프 위젯을 레이아웃에 추가합니다.
            vertical_layout.addStretch(1)  # 레이아웃에 여유 공간을 추가합니다.

        old_calendar = self.findChild(QCalendarWidget, "calendarWidget")  # 기존 달력 위젯을 찾습니다.
        if old_calendar:
            self.custom_calendar = CustomCalendar(self)  # 사용자 정의 달력을 만듭니다.
            self.custom_calendar.setGeometry(old_calendar.geometry())  # 기존 달력의 위치와 크기를 복사합니다.
            self.custom_calendar.setObjectName("calendarWidget")  # 기존 달력과 동일한 이름을 설정합니다.
            layout = old_calendar.parentWidget().layout()  # 기존 달력의 부모 위젯에서 레이아웃을 찾습니다.
            if layout:
                layout.replaceWidget(old_calendar, self.custom_calendar)  # 기존 달력을 새 달력으로 교체합니다.
            old_calendar.deleteLater()  # 기존 달력을 삭제합니다.

if __name__ == "__main__":  # 이 부분은 프로그램 실행 시 최초로 호출되는 코드입니다.
    app = QApplication(sys.argv)  # QApplication 객체를 생성하여 프로그램 실행 준비를 합니다.
    window = AttendanceApp()  # 출석 관리 앱 창을 생성합니다.
    window.show()  # 창을 화면에 표시합니다.
    sys.exit(app.exec_())  # 이벤트 루프를 실행하여 애플리케이션을 실행합니다.
import cx_Oracle as oci
from datetime import datetime, timedelta
import random

# 오라클 DB 접속 정보
sid = 'XE'
host = '127.0.0.1'
port = 1521
username = 'attendance'
password = '12345'

def calculate_status(atd_time):
    # 출석 상태 계산 함수
    # 7시~8시 사이 출석(P)으로 설정
    hour = atd_time.hour
    if 7 <= hour < 9:
        return 'P'  # 출석
    else:
        return 'P'  # 그 외 시간대도 출석으로 설정

def random_time(current_date):
    # 랜덤 시간 생성 (7시~8시)
    # 출석만 나오게 설정
    time = current_date.replace(hour=random.randint(7, 8), minute=random.randint(0, 59))
    return time

def insert_august_attendance():
    print("8월 출석 데이터 삽입 시작")

    conn = oci.connect(f'{username}/{password}@{host}:{port}/{sid}')
    cursor = conn.cursor()

    try:
        conn.begin()

        # s_no = 1인 학생과 t_no = 1인 교사 확인
        cursor.execute("SELECT S_NO, CLASS_NO FROM ATTENDANCE.STUDENT WHERE S_NO = 1")
        student = cursor.fetchone()

        cursor.execute("SELECT T_NO, CLASS_NO FROM ATTENDANCE.TEACHER WHERE T_NO = 1")
        teacher = cursor.fetchone()

        if not student or not teacher:
            print("학생 또는 교사를 찾을 수 없습니다.")
            return

        s_no, student_class_no = student
        t_no, teacher_class_no = teacher

        if student_class_no != teacher_class_no:
            print("학생과 교사의 CLASS_NO가 일치하지 않습니다.")
            return

        # 출석 데이터 생성 (2025년 8월 1일 ~ 8월 31일, 월~금만)
        attendance_data = []

        start_date = datetime(2025, 8, 1)
        end_date = datetime(2025, 8, 31)
        current_date = start_date

        total_days = 0
        total_o = 0  # 출석(동그라미)
        total_l = 0  # 지각(세모)
        l_dates = set()  # 'L' 상태의 날짜를 저장할 집합

        while current_date <= end_date:
            if current_date.weekday() < 5:  # 월~금만
                total_days += 1

                # 항상 출석 상태로 설정 (P)
                status = 'P'  # 출석

                # 시간에 맞는 출석 상태 계산
                atd_time = random_time(current_date)
                status = calculate_status(atd_time)  # 출석 상태 계산

                attendance_data.append((s_no, current_date.strftime('%Y-%m-%d'), atd_time.strftime('%Y-%m-%d %H:%M:%S'), t_no, status))

            current_date += timedelta(days=1)

        # ATD 테이블에 삽입
        query = '''
            INSERT INTO ATTENDANCE.ATD (ATD_no, S_NO, ATD_DATE, ATD_TIME, T_NO, STATUS) 
            VALUES(ATTENDANCE.atd_no_seq.nextval, :1, TO_DATE(:2, 'YYYY-MM-DD'), TO_DATE(:3, 'YYYY-MM-DD HH24:MI:SS'), :4, :5)
        '''

        cursor.executemany(query, attendance_data)
        conn.commit()

        print(f"ATD 테이블에 {len(attendance_data)}개 출석 데이터 삽입 완료 (8월 한 달간, 월~금만 기록, 모두 출석(P))")

    except Exception as e:
        print("출석 데이터 삽입 오류 발생:", e)
        conn.rollback()

    finally:
        cursor.close()
        conn.close()

if __name__ == "__main__":
    # 8월 출석 데이터 삽입
    insert_august_attendance()