import codecs
from jinja2 import Template
import string
import glob
import multiprocessing
from pymoo.algorithms.soo.nonconvex.ga import GA
from pymoo.optimize import minimize
import PySimpleGUI as sg
from flask import Flask, render_template
import pandas as pd
import csv
import numpy as np
from pulp import *
from difflib import get_close_matches
import urllib.request
from bs4 import BeautifulSoup
import re
import itertools
import reprlib
import requests
import regex
import time
from pulp import LpProblem, LpMaximize, lpDot, lpSum, value, GLPK_CMD
from ortoolpy import addvar, addvars, addbinvars
from multiprocessing.pool import ThreadPool
from dask.distributed import Client
import numpy as np
from pymoo.util.misc import stack
from pymoo.core.problem import Problem
from pymoo.algorithms.moo.nsga2 import NSGA2
from pymoo.factory import get_sampling, get_crossover, get_mutation, get_termination, get_problem
from pymoo.optimize import minimize
from pymoo.visualization.scatter import Scatter
from pymoo.core.problem import ElementwiseProblem, dask_parallelized_eval
from pymoo.core.crossover import Crossover
from pymoo.core.mutation import Mutation 
from pymoo.core.sampling import Sampling
from pymoo.algorithms.soo.nonconvex.ga import GA
from pymoo.optimize import minimize
from pymoo.util.display import Display
import matplotlib.pyplot as plt
from PIL import Image
import shutil


ninzu = int(1)
name = str("sumple")
age = int(22)
height=float(166)
weight = float(66)
gender = str("男")
actlevel = str('低い')

# print('人数は1人')
# print('name:'+name)
# print("age:"+str(age))
# print("height:"+str(height))
# print("weight:"+str(weight))
# print("gender:"+gender)

R = 400

print("れしぴの数:"+str(R))

if age <= 17:
    if actlevel == "低い":
        actlevel = 1.55
    if actlevel == "普通":
        actlevel = 1.75
    if actlevel == "高い":
        actlevel = 1.95

if 18 <= age <= 29:
    if actlevel == "低い":
        actlevel = 1.55
    if actlevel == "普通":
        actlevel = 1.75
    if actlevel == "高い":
        actlevel = 1.95

if 30 <= age:
    if actlevel == "低い":
        actlevel = 1.45
    if actlevel == "普通":
        actlevel = 1.70
    if actlevel == "高い":
        actlevel = 1.95

#bms基礎代謝基準値
if gender == "男":
    if age <= 17:
        bms = 27.0

    if 18 <= age <= 29:
        bms = 24.0

    if 30 <= age:
        bms = 21.5

if gender == "女":
    if age <= 17:
        bms = 25.3

    if 18 <= age <= 29:
        bms = 22.1

    if 30 <= age:
        bms = 20.7

print("actlevel:"+str(actlevel))
#基礎代謝量
fma = bms * weight

#必要推定エネルギー量
eer = fma * actlevel

#必要タンパク質
rtanpaku = (eer * 0.13) / 4

#必要脂質
rsisitu = (eer * 0.15) / 9

#必要炭水化物
rtansui = (eer * 0.4) / 4

##############################################ここまでレシピを作る人数を入力する画面#######################################

#################ここからcsvからレシピのデータベースを作成#####################################################################
#t1 = time.time()
####変数のセット####
#レシピの数R

gen = 20
#レシピの内部番号i(0~399)
i = list(range(R))
#レシピ番号num(1~400)
num = []
for n in i:
    num.append(n+1)  


#食材の数Q
Q = 0
#栄養素の数N
N = 0
#レシピナンバー
recinoi = list(range(R))
#レシピの名前yi
yi = list(range(R))
#調理時間Ti
Ti = list(range(R))
#主菜フラグsigmai
sigmai = list(range(R))
#摂取カロリーCi
Ci = list(range(R))
#コストGi
Gi = list(range(R))
#材料名qij
qij = [['' for i in range(25)] for j in range(R)]

mf = list(range(R))
#vはcsvから入手する用の変数
v = list(range(25))
Nm = 0
Qm = 0

##########################################ここからcsvからレシピのデータベースを作成#####################################################################
#t1 = time.time()
#使うレシピデータリスト
for j in i:

    #csvの読み取り
    df = pd.read_csv("../recipe_data/recipe_no"+ str(j+1) +".csv",encoding="cp932")    
    for m in v:
	#ilocは行と列を指定することによりその値を抽出できる
    #CSVから栄養素、食材を読み込み
        #材料名qij
        qij[j][m] = df.iloc[m,4]
        #データの成型（数値しか入らないように単位を消す、単位をそろえる）
        if('(A)'or '(a)'or'(B)'or '(b)'or'・' in qij[j][m]):
            qij[j][m] = qij[j][m].replace('(A)','NAN')
            qij[j][m] = qij[j][m].replace('(a)','NAN')
            qij[j][m] = qij[j][m].replace('(B)','NAN')
            qij[j][m] = qij[j][m].replace('(b)','NAN')
            qij[j][m] = qij[j][m].replace('・','')

        yi[j] = df.iloc[1,0]
        #NANじゃなかったら食材数を数える
        if not qij[j][m]=="NAN":
            Q = Q + 1        

#インデックス作成
index1 = list(range(R))
#pd_nameはレシピ名と食材が入ったdataframe
print("データフレーム作成しました")
pd_name = pd.DataFrame(data=qij,index = index1)
pd_name.insert(0,'name',yi)
pd_name.insert(0,'number',i)
#pd_name.to_csv('recipename.csv', encoding="shift_jis",errors='ignore', index=False)
########################################ここまでdataframe作成#########################


#xが含まれる食材番号をyの配列に入れる関数の定義
def kensaku(x,y):
    c = []
    for j in range(24):
        #xが含まれる行を特定
        a = pd_name[j].str.contains(x)
        #xが含まれる行番号を取得
        b = pd_name.loc[a,'number']
        c = b.values.tolist()
        y.extend(c)
    return y

arerugi = []
#print("アレルギーなし")
##############ここまででarerugiにアレルギーが含まれるレシピ番号を格納した。######################################################



day = int(1)
timea = int(30)
timeb = int(60)
timec = int(60)

print(str(day)+"日分作る")
print("朝の時間"+str(timea))
print("昼の時間"+str(timeb))
print("夜の時間"+str(timec))
########################################出力する日数の設定おわり###############################################################################
print("レシピデータを作成します")
########################################ここからレシピのデータベースの作成######################################################################
gen = 10
#レシピの番号i
i = list(range(R))
#食材の数Q
Q = 0

#栄養素の数N
N = 0
#レシピナンバー
recinoi = list(range(R))

#レシピの名前yi
yi = list(range(R))
#調理時間Ti
Ti = list(range(R))
#主菜フラグsigmai
sigmai = list(range(R))
#摂取カロリーCi
Ci = list(range(R))
#コストGi
Gi = list(range(R))
#材料量e
eij = [['' for i in range(25)] for j in range(R)]
#栄養素名nij
nij = [['' for i in range(25)] for j in range(R)]
#栄養素量fij
fij = [['' for i in range(25)] for j in range(R)]

mf = list(range(R))

#vはcsvから入手する用の変数
v = list(range(25))
Nm = 0
Qm = 0
for j in i:

    #if(j==0):
        #continue
    #################################################################################################
    df = pd.read_csv("../recipe_data/recipe_no"+ str(j+1) +".csv",encoding="cp932")
    #################################################################################################
    
    for m in v:
	#ilocは行と列を指定することによりその値を抽出できる
    #CSVから栄養素、食材を読み込み
        eij[j][m] = df.iloc[m,5]
        #栄養素名nij
        nij[j][m] = df.iloc[m,6]
        #栄養素量fij
        fij[j][m] = df.iloc[m,7]
        
        #データの成型（数値しか入らないように単位を消す、単位をそろえる）
        if ('mg' in fij[j][m]):
            fij[j][m] = fij[j][m].replace('mg','')
            fij[j][m] = fij[j][m].replace(',','.')
            fij[j][m] = str(fij[j][m])

        if ('μg' in fij[j][m]):#
            fij[j][m] = fij[j][m].replace('μg','')
            fij[j][m] = fij[j][m].replace(',','.')
            fij[j][m] = str(fij[j][m])
        
        if ('g' in fij[j][m]):
            fij[j][m] = fij[j][m].replace('g','')
            fij[j][m] = fij[j][m].replace(',','.')
            fij[j][m] = str(fij[j][m])

        
        fij[j][m] = float(fij[j][m])
        yi[j] = df.iloc[1,0]
        Gi[j] = df.iloc[1,8]
        Ti[j] = df.iloc[1,2]
        sigmai[j] = df.iloc[1,1]
        Ci[j] = df.iloc[1,3]
        
        N = N + 1
        ###mf朝食フラグ
        mf[j] = df.iloc[1,9]
        ###############################################################
        
        #NANじゃなかったら食材数を数える
        if not qij[j][m]=="NAN":
            Q = Q + 1        


#辞書データの作成
dict1 = {'レシピNo':i,"レシピの名前":yi,"主菜フラグ":sigmai,"調理時間":Ti,"摂取カロリー":Ci,"コスト":Gi}
#レシピデータのデータフレーム作成
df_reci = pd.DataFrame(data = dict1)
#栄養素のデータフレーム作成
df_fij = pd.DataFrame(data = fij,columns = ['たんぱく質',
'炭水化物','糖質','脂質','食塩相当量',
'食物繊維','ビタミンA','ビタミンB1','ビタミンB2','ビタミンB6',
'ビタミンB12','ビタミンC','ビタミンD','ビタミンE','ビタミンK',
'カリウム','カルシウム','鉄','葉酸','マグネシウム','リン','亜鉛',
'パントテン酸','銅','マンガン'])

df_recipe = df_reci.join(df_fij)#df_recipeは料理名と栄養素の名前が入っているdataframe
#print("レシピデータ:")
#print(df_recipe)
#df_recipe.to_csv('recipedata.csv', encoding="shift_jis",errors='ignore', index=False) #レシピデータをcsvに保存(確認用)
df_recipe['献立フラグ'] = addvars(len(df_recipe))

if arerugi:#アレルギーの配列に数字が入ってる場合実行
    df_recipe =df_recipe.drop(arerugi)#アレルギーの配列に入ってる数字を削除
    print("アレルギーを削除した後のレシピデータ:")
    R = len(df_recipe.index)#データ数の変更
    print("データ数R:")
    print(R)
    df_recipe['レシピNo'] =list(range(R))
    yi = df_recipe['レシピの名前'].values.tolist()#レシピの名前を変更
    Gi = df_recipe['コスト'].values.tolist()#コストを変更
    Ti = df_recipe['調理時間'].values.tolist()#調理時間を変更
    sigmai = df_recipe['主菜フラグ'].values.tolist()#主菜フラグを変更
    Ci = df_recipe['摂取カロリー'].values.tolist()#摂取カロリーを変更
    mf = list(range(R))
    print('カロリーの個数'+ str(len(Ci)))
    #df_recipe.to_csv('recipedata2.csv', encoding="shift_jis",errors='ignore', index=False)


#最適化に使う各栄養素をリスト化する
f0j = df_recipe['たんぱく質'].values.tolist()#g
f1j = df_recipe['脂質'].values.tolist()#g
f2j = df_recipe['炭水化物'].values.tolist()#g
f3j = df_recipe['糖質'].values.tolist()#g
f4j = df_recipe['食塩相当量'].values.tolist()#g
f5j = df_recipe['食物繊維'].values.tolist()#g
f6j = df_recipe['ビタミンA'].values.tolist()#μg
f7j = df_recipe['ビタミンB1'].values.tolist()#mg
f8j = df_recipe['ビタミンB2'].values.tolist()#mg
f9j = df_recipe['ビタミンB6'].values.tolist()#mg
f10j = df_recipe['ビタミンB12'].values.tolist()#μg
f11j = df_recipe['ビタミンC'].values.tolist()#mg
f12j = df_recipe['ビタミンD'].values.tolist()#μg
f13j = df_recipe['ビタミンE'].values.tolist()#mg
f14j = df_recipe['ビタミンK'].values.tolist()#μg
f15j = df_recipe['カリウム'].values.tolist()#mg
f16j = df_recipe['カルシウム'].values.tolist()#mg
f17j = df_recipe['鉄'].values.tolist()#mg
f18j = df_recipe['葉酸'].values.tolist()#μg
f19j = df_recipe['マグネシウム'].values.tolist()#mg
f20j = df_recipe['リン'].values.tolist()#mg
f21j = df_recipe['亜鉛'].values.tolist()#mg
f22j = df_recipe['パントテン酸'].values.tolist()#mg
f23j = df_recipe['銅'].values.tolist()#mg
f24j = df_recipe['マンガン'].values.tolist()#mg

for h in range(len(f0j)):
    f0j[0] = 0
    f1j[0] = 0
    f2j[0] = 0
    f3j[0] = 0
    f4j[0] = 0
    f5j[0] = 0
    f6j[0] = 0
    f7j[0] = 0
    f8j[0] = 0
    f9j[0] = 0
    f10j[0] = 0
    f11j[0] = 0
    f12j[0] = 0
    f13j[0] = 0
    f14j[0] = 0
    f15j[0] = 0
    f16j[0] = 0
    f17j[0] = 0
    f18j[0] = 0
    f19j[0] = 0
    f20j[0] = 0
    f21j[0] = 0
    f22j[0] = 0
    f23j[0] = 0
    f24j[0] = 0


#ゴミとり
for g in range(len(Ci)):
    Ci[g] = str(Ci[g]).replace(',', '')
    Ci[g] = int(Ci[g])

#それぞれのリストをnp配列に変換
Gi = np.array(Gi)
Ti = np.array(Ti)
Ci = np.array(Ci)
f0j = np.array(f0j)
f1j = np.array(f1j)
f2j = np.array(f2j)
f3j = np.array(f3j)
f4j = np.array(f4j)
f5j = np.array(f5j)
f6j = np.array(f6j)
f7j = np.array(f7j)
f8j = np.array(f8j)
f9j = np.array(f9j)
f10j = np.array(f10j)
f11j = np.array(f11j)
f12j = np.array(f12j)
f13j = np.array(f13j)
f14j = np.array(f14j)
f15j = np.array(f15j)
f16j = np.array(f16j)
f17j = np.array(f17j)
f18j = np.array(f18j)
f19j = np.array(f19j)
f20j = np.array(f20j)
f21j = np.array(f21j)
f22j = np.array(f22j)
f23j = np.array(f23j)
f24j = np.array(f24j)
sigmai = np.array(sigmai)
mf = np.array(mf)

print("データ作成完了\n")
print("処理を開始します。")
#################ここまでcsvからレシピのデータベースを作成####################################################



#################ここからnsga2の変数設定#####################################################################
#client = Client("tcp://192.168.11.2:8786")


#Subsetproblemは組み合わせ最適化問題を用いるときに使うclass
#problem = SubsetProblem(Ti, n_max,c_max,Gi,Ci,f0j,f1j,f2j,sigmai,mf,eer,rtanpaku,rsisitu,rtansui)
class SubsetProblem(ElementwiseProblem):
    def __init__(self,
                 T,
                 n_max,
                 c_max,
                 cost,
                 cal,
                 f0,
                 f1,
                 f2,
                 sigmai,
                 mf,
                 day,
                 timea,
                 timeb,
                 timec,
                 eer,
                 tanpakumin,
                 sisitumin,
                 tansuimin,
                 elementwise_evaluation=True,
                 *args,
                 **kwargs
                 ):
        super().__init__(n_var=len(T),# 入力変数の次元(選ぶレシピの数)
                         n_obj=2,# 目的関数の数
                        n_constr=13,# 制約条件の数
                        *args,#*argsは複数の因数をタプルとして受け取る。
                        **kwargs)#**kwargsは複数の値を辞書（ディクショナリ）として受け取る
        
    #関数内で使用する変数の設定 
    #T時間,#nmax選ぶレシピの数,c_max,COSTコスト,Cカロリー,f0タンパク質,f1脂質,f2炭水化物,sigmai主菜フラグ,mf必要栄養素の変数
    #eer推定エネルギー,tanpakumin必要タンパク質,sisitumin必要脂質,tansuimin必要炭水化物の変数
        self.T = T
        self.n_max = n_max
        self.c_max = c_max
        self.cost = cost
        self.cal = cal
        self.f0 = f0
        self.f1 = f1
        self.f2 = f2
        self.sigmai = sigmai
        self.mf = mf
        self.day = day
        self.timea = timea
        self.timeb = timeb
        self.timec = timec
        self.eer = eer
        self.tanpakumin = tanpakumin,
        self.sisitumin = sisitumin,
        self.tansuimin = tansuimin,

    def _evaluate(self, x, out, *args, **kwargs):
        #目的関数、制約条件の定義とそれに使う変数の定義
        #必要タンパク質
        f0jmin = rtanpaku #70

    	#必要脂質 
        f1jmin = rsisitu #38

    	#必要炭水化物
        f2jmin = rtansui #283
        f0jmax = 100
        f1jmax = 100
        f2jmax = 300
        
        #必要カロリーとカロリーの最大値
        calmax = 2500
        calmin = 2000
        calmin = eer - 100 #2500
        calmax = eer + 400 #3000
        #最大時間
        timemax1 = timea#朝
        timemax2 = timeb#昼
        timemax3 = timec#夜
        #コスト,カロリー,たんぱく質,脂質,炭水化物の初期値設定
        cost1 = 0
        cal1 = 0
        tanpaku1 = 0
        sisitu1 = 0
        tansui1 = 0

        p = list(range(200))
        #f1は選んだレシピの時間の合計
        f1 = np.sum(self.T[x])
        #f2は選んだレシピのコストの合計
        f2 = np.sum(self.cost[x])
        #gnはxの選ぶ個数(49個)
        gn = self.n_max - np.sum(x)

        #朝の調理時間
        time1 = self.T[x][0]
        #昼の調理時間の合計
        time2 = self.T[x][1] + self.T[x][2] + self.T[x][3]
        #夜の調理時間の合計
        time3 = self.T[x][4] + self.T[x][5] + self.T[x][6]

        
        for i in range(7):
            #1日のたんぱく質の合計
            tanpaku1 += self.f0[x][i]
            #1日の脂質の合計
            sisitu1 += self.f1[x][i]
            #1日の炭水化物の合計
            tansui1 += self.f2[x][i]
            #1日のカロリーの合計
            cal1 += self.cost[x][i]
            #1日のコストの合計
            cost1 += self.cost[x][i]
        

        p[0] = 24 - np.sum(self.sigmai[x])
        p[1] = np.sum(self.sigmai[x]) - 30
        #f0min - tanpaku1 <=0 → f0min(必要タンパク質) <tanpaku
        p[2] = f0jmin - tanpaku1

        #f1min - sisitu1 <=0 → f1min(必要脂質) <sisitu
        p[3] = f1jmin - sisitu1

        #f2min - tansui <=0 → f2min(必要炭水化物) <tansui
        p[4] = f2jmin - tansui1

        #cmin - cost1 <=0 → cmin(必要カロリー) < cost1
        p[5] = calmin - cal1
        
        #time1 - timemax1 <=0 → time1 <timemax1(時間の最大値,45)
        p[6] = time1 - timemax1

        #time2 - tomemax2 <=0 → time2 <timemax2(時間の最大値2,60)
        p[7] = time2 - timemax2

        p[8] = time3 - timemax3

        #1- self.sigmai[x][0] <=0 →  1<=self.sigmai[x][0] 昼の1品目の主菜フラグが1
        p[9] = 1 - self.sigmai[x][1]

        #self.sigmai[x][0] -0.5<= 0 →  self.sigmai[x][0]<=0.5 昼の2品目と3品目の主菜フラグが0
        p[10] = self.sigmai[x][2] - 0.5
        p[11] = self.sigmai[x][3] - 0.5

        #cal1 - cmax < 0 → cal1< cmax(カロリーの最大摂取量) 
        p[12] = cal1 - calmax

        #1 - self.mf[x][0]<=0 → 1 <= self.mf[x][0] 朝食のフラグが1になる
        p[13] = 1 - self.mf[x][0]

        out["F"] = [f1,f2]#二つの目的関数,コスト,時間の合計
        out["G"] = [#p[1],
        #p[2],p[3],p[4],p[5],p[6],p[7],p[8],p[9],p[10],p[11],p[12],
        p[13]
                    ]

#サンプリングは、サブセット制約が常に満たされるランダムなソリューションを作成する
class MySampling(Sampling):

    def _do(self, problem, n_samples, **kwargs):
        X = np.full((n_samples, problem.n_var), False, dtype=bool)

        for k in range(n_samples):
            I = np.random.permutation(problem.n_var)[:problem.n_max]
            X[k, I] = True

        return X

#クロスオーバーは両方の親の値を取得し、十分な数が選択されるまで、最初または 2 番目の親からいずれかをランダムに選択します。
class BinaryCrossover(Crossover):
    def __init__(self):
        super().__init__(2, 1)

    def _do(self, problem, X, **kwargs):
        n_parents, n_matings, n_var = X.shape

        _X = np.full((self.n_offsprings, n_matings, problem.n_var), False)

        for k in range(n_matings):
            p1, p2 = X[0, k], X[1, k]

            both_are_true = np.logical_and(p1, p2)
            _X[0, k, both_are_true] = True

            n_remaining = problem.n_max - np.sum(both_are_true)

            I = np.where(np.logical_xor(p1, p2))[0]

            S = I[np.random.permutation(len(I))][:n_remaining]
            _X[0, k, S] = True

        return _X

#ミューテーションはランダムに数字を削除し、別の数字を選択します。
class MyMutation(Mutation):
    def _do(self, problem, X, **kwargs):
        for i in range(X.shape[0]):
            X[i, :] = X[i, :]
            is_false = np.where(np.logical_not(X[i, :]))[0]
            is_true = np.where(X[i, :])[0]
            X[i, np.random.choice(is_false)] = True
            X[i, np.random.choice(is_true)] = False

        return X

# create the actual problem to be solved
np.random.seed(1)
cvavg = list()
cvmin = list()

Gi = np.array(Gi)
Ti = np.array(Ti)
Ci = np.array(Ci)
f0j = np.array(f0j)
f1j = np.array(f1j)
f2j = np.array(f2j)
###############################################################
n_max = 7*day#nmaxはレシピの数(1日7品,７日分で49個)
###############################################################
c_max = 300

print("start")
n_process = 8 # プロセス数
pool = multiprocessing.Pool(n_process) # 初期化
problem = SubsetProblem(Ti, n_max,c_max,Gi,Ci,f0j,f1j,f2j,sigmai,mf,day,timea,timeb,timec,eer,rtanpaku,rsisitu,rtansui,parallelization = ('starmap', pool.starmap))
#problem = SubsetProblem(Ti, n_max,c_max,Gi,Ci,f0j,f1j,f2j,sigmai,parallelization=("dask",client))

#################ここまでnsga2の変数設定#####################################################################
algorithm = NSGA2(
    pop_size=R,
    sampling=MySampling(),
    crossover=BinaryCrossover(),
    mutation=MyMutation(),
    eliminate_duplicates=True)

res = minimize(problem,
               algorithm,
               ('n_gen',gen),
               seed=1,
               #display=MyDisplay(),
               verbose=False)

#np.set_printoptions()でnpの表示数の設定している。
#threshold=np.inf(無限大)で常に省略されずすべての要素が表示される。
np.set_printoptions(threshold=np.inf)
print('res.x')
print(res.X)

print("プロセス数:"+str(n_process))
print(f'multiprocesstime:{res.exec_time}秒')

# print('res.F')
# print(res.F)


T_all = list(range(len(res.F)))
G_all = list(range(len(res.F)))


# print('res.f')
# print(res.F)
# Scatter().add(res.F).show()
number = list()

#最適化された献立の番号をnumberの配列に入れる
for c in range(R):#レシピ数
    if res.X[0][c] == True:
        number.append(c)

print('レシピ番号')
print(number)
print("finish")