#this is the task module
import numpy as np

#print stars
def stars(number):
    for i in range(number):
        print("*", end = "")
    print("")

#error messages
def errorCodeMsg():
    print("Error in input file : CODE ")
    quit()
    

def errorPredMsg():
    print("Error in input file : PREDECESSORS ")
    quit()

def errorDaysMsg():
    print("Error in input file : DAYS ")
    quit()

# Scans if the code in predecessors and successors are
# in the list of task codes:
def getTaskCode(mydata, code):
    x = 0
    flag = 0
    for i in mydata['CODE']:
        if(i == code):
            flag = 1
            break
        
        x+=1
    
    if(flag == 1):
        return x
    else:
        errorCodeMsg()



# Critical Path Method Forward Pass Function
# EF -> Earliest Finish
# ES -> Earliest Start

def forwardPass(mydata):
    ntask = mydata.shape[0]
    ES = np.zeros(ntask, dtype=np.int32)
    EF = np.zeros(ntask, dtype=np.int32)
    temp = []  # hold temporary codes

    for i in range(ntask):
        if mydata['PREDECESSORS'][i] is None:
            ES[i] = 0
            try:
                EF[i] = ES[i] + mydata['DAYS'][i]
            except Exception as e:
                print(f"Error in task {i+1}: {e}")
                errorDaysMsg()

        else:
            for j in mydata['PREDECESSORS'][i]:
                index = getTaskCode(mydata, j)
                # Check the video for the missing line here

                if index == i:
                    print(f"Error in task {i+1}: {errorPredMsg()}")
                else:
                    temp.append(EF[index])

            ES[i] = max(temp)
            try:
                EF[i] = ES[i] + mydata['DAYS'][i]
            except Exception as e:
                print(f"Error in task {i+1}: {e}")
                errorDaysMsg()

            # reset temp
            temp = []

    # Update dataFrame:
    mydata['ES'] = ES
    mydata['EF'] = EF

    return mydata



# Critical Path Method Backward Pass function
# LS -> Latest Start
# LF -> Latest Finish

def backwardPass(mydata):
    ntask = mydata.shape[0]
    temp = []
    LS = np.zeros(ntask, dtype=np.int32)
    LF = np.zeros(ntask, dtype=np.int32)
    SUCCESSORS = np.empty(ntask, dtype = object)

    #create successor column:
    
    # create successor column:
    for i in range(ntask-1, -1, -1):
        if(mydata['PREDECESSORS'][i] != None):
            for j in mydata['PREDECESSORS'][i]:
                index = getTaskCode(mydata, j)
                if(SUCCESSORS[index] != None):
                    SUCCESSORS[index].append(mydata['CODE'][i])
                else:
                    SUCCESSORS[index] = [mydata['CODE'][i]]

    # incorporate the column to the data frame:
    mydata["SUCCESSORS"] = SUCCESSORS

    

    #compute for  EF and LS:

    for i in range(ntask-1, -1, -1):
        if(mydata['SUCCESSORS'][i] == None):
            LF[i] = np.max(mydata['EF'])
            LS[i] = LF[i] - mydata['DAYS'][i]
        else:
            for j in mydata['SUCCESSORS'][i]:
                index = getTaskCode(mydata,j)
                temp.append(LS[index])

            LF[i] = min(temp)
            LS[i] = LF[i] - mydata['DAYS'][i]

            #reset temp list:
            temp = []


    #incorporate LF and LS to data frame :

    mydata['LS'] = LS
    mydata['LF'] = LF

    return mydata

#compute for SLACK and CRITICAL state
def slack(mydata):
    ntask = mydata.shape[0]
    
    SLACK = np.zeros(shape = ntask, dtype=np.int32)
    CRITICAL = np.empty(shape = ntask,dtype = object)

    for i in range(ntask):
        SLACK[i] = mydata['LS'][i] - mydata['ES'][i] 
        if(SLACK[i] == 0):
            CRITICAL[i] = "YES"
        else:
            CRITICAL[i] = "NO"

    #incorporate SLACK and CRITICAL to data frame

    mydata['SLACK'] = SLACK
    mydata['CRITICAL'] = CRITICAL


    #re arrange columns in dataframe:
    mydata = mydata.reindex(columns = ['DESCR', 'CODE','PREDECESSORS',
                'SUCCESSORS','DAYS','ES','EF','LS','LF','SLACK','CRITICAL'])

    return mydata

#wrapper function:

def computeCPM(mydata):
    print("Before forwardPass:")
    print(mydata)
    mydata = forwardPass(mydata)
    mydata = backwardPass(mydata)
    mydata = slack(mydata)
    return mydata

#print function

def printTask(mydata):
    print("CRITICAL PATH METHOD CALCULATOR")
    stars(90)
    print("ES = Earliest Start; EF = Earliest Finish; LS = Latest Start, LF = Latest Finish")
    stars(90)
    print(mydata)
    stars(90)


import pandas as pd

# ori.csv からデータを読み込む
df_ori = pd.read_csv('float.csv', delimiter=',')

# データの整形
data = {
    'DESCR': df_ori['単元名'],
    'CODE': df_ori['単元番号'].astype(str),  # 数値から文字列に変換
    'PREDECESSORS': df_ori['前単元'].apply(lambda x: None if pd.isna(x) else [str(int(i)) for i in x.split(',')]),
    'DAYS': df_ori['指導時数']
}

# データフレームを作成
df = pd.DataFrame(data)


print("Input Data:")
print(df)
# CPM計算を実行
result_data = computeCPM(df)

# 結果を表示
printTask(result_data)

# クリティカルなノードのみを取得
critical_nodes = result_data[result_data['CRITICAL'] == 'YES']

# 結果を格子枠線のある表で表示
print(result_data.to_markdown(index=False, tablefmt='grid'))


# クリティカルパス上の経路を表示
print("\nCritical Path Route:")
for index, row in critical_nodes.iterrows():
    print(f"({row['CODE']}) -> ", end="")
print("End")
