import base64
import io

import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output

import numpy as np
import tensorflow as tf
from PIL import Image

from constants import CLASSES

import yaml 
with open('app.yaml') as yaml_data :
    params = yaml.safe_load(yaml_data)
    
IMAGE_WIDTH = params['IMAGE_WIDTH']
IMAGE_HEIGHT = params['IMAGE_HEIGHT']
PATH_MODEL = params['PATH_MODEL']


# Load DNN model
classifier = tf.keras.models.load_model(PATH_MODEL)

def classify_image(image, model, image_box=None):
  """Classify image by model

  Parameters
  ----------
  content: image content
  model: tf/keras classifier

  Returns
  -------
  class id returned by model classifier
  """
  images_list = []
  image = image.resize((IMAGE_WIDTH, IMAGE_HEIGHT), box=image_box)
                                        # box argument clips image to (x1, y1, x2, y2)
  image = np.array(image)
  images_list.append(image)
  
  return model.predict_classes(np.array(images_list))


app = dash.Dash('Traffic Signs Recognition', external_stylesheets=[dbc.themes.BOOTSTRAP])


pre_style = {
    'whiteSpace': 'pre-wrap',
    'wordBreak': 'break-all',
    'whiteSpace': 'normal'
}


# Define application layout


navbar = dbc.NavbarSimple(
    children=[
        dbc.DropdownMenu(
            children=[
                dbc.DropdownMenuItem('Réseau de Neurones', header=True),
                dbc.DropdownMenuItem('SVM', href="#"),
            ],
            nav=True,
            in_navbar=True,
            label='Modèle',
        ),
    ],
    brand="Menu",
    brand_href="#",
    color= "#d90054",
    dark=True
)

cards = html.Div(  
    [
        dbc.Card(
            dbc.CardBody(
                [
                    html.H5("Présentation", className="card-title"),
                    html.P(
                        [
                            'Cette application à pour but de réaliser des modèles capables de classer des panneaux de signalisation allemand à partir d\'une image. L\'application fonctionne de la manière suivante : vous déposer une image à l\'emplacement indiqué et la prédiction du modèle apparait immédiatement en dessous. En haut à droite vous pouvez sélectionner le modèle que vous voulez tester.',
                        ],
                        className='card-text',
                    ),
                ]
            ),
            className='w-75 mb-3', 
            color='#f1cbd1', 
            outline='Black',  
            style={
                'margin-top': '75px',  
                'margin-left': '185px'}, 
        ),
    ]
)

app.layout = html.Div([
    html.Div([navbar]),
    
    html.Div(cards),
    dcc.Upload(
        id='bouton-chargement',
        children=html.Div([
            'Cliquer-déposer ou ',
                    html.A('sélectionner une image')
        ]),
        style={
            'width': '50%',
            'height': '60px',
            'lineHeight': '60px',
            'borderWidth': '1px',
            'borderStyle': 'dashed',
            'borderRadius': '5px',
            'textAlign': 'center',
            'margin-top': '75px',
            'margin-left': '370px', 
        }
    ),
    html.Div(id='mon-image'),
    html.Div(id='ma-zone-resultat')
])

@app.callback(Output('mon-image', 'children'),
              [Input('bouton-chargement', 'contents')])
def update_output(contents):
    if contents is not None:
        content_type, content_string = contents.split(',')
        if 'image' in content_type:
            image = Image.open(io.BytesIO(base64.b64decode(content_string)))
            predicted_class = classify_image(image, classifier)[0]
            return html.Div([
                html.Hr(style={'margin-top': '75px'}),
                html.Img(src=contents, style={'margin-left': '750px'}),
                html.H4('Classe prédite : {}'.format(CLASSES[predicted_class]), style={'textAlign': 'center'}),
                html.Hr(),
                #html.Div('Raw Content'),
                #html.Pre(contents, style=pre_style)
            ])
        else:
            try:
                # Décodage de l'image transmise en base 64 (cas des fichiers ppm)
                # fichier base 64 --> image PIL
                image = Image.open(io.BytesIO(base64.b64decode(content_string)))
                # image PIL --> conversion PNG --> buffer mémoire 
                buffer = io.BytesIO()
                image.save(buffer, format='PNG')
                # buffer mémoire --> image base 64
                buffer.seek(0)
                img_bytes = buffer.read()
                content_string = base64.b64encode(img_bytes).decode('ascii')
                # Appel du modèle de classification
                predicted_class = classify_image(image, classifier)[0]
                # Affichage de l'image
                return html.Div([
                    html.Hr(style={'margin-top': '75px'}),
                    html.Img(src='data:image/png;base64,' + content_string, style={'margin-left': '750px'}),
                    html.H4('Classe prédite : {}'.format(CLASSES[predicted_class]), style={'textAlign': 'center'}),
                    html.Hr(),
                ])
            except:
                return html.Div([
                    html.Hr(),
                    html.Div('Uniquement des images svp : {}'.format(content_type)),
                    html.Hr(),                
                    html.Div('Raw Content'),
                    html.Pre(contents, style=pre_style)
                ])
            

# Manage interactions with callbacks
@app.callback(
    Output(component_id='ma-zone-resultat', component_property='children'),
    [Input(component_id='mon-champ-texte', component_property='value')]
)
def update_output_div(input_value):
    return html.H3('Valeur saisie ici "{}"'.format(input_value))


# Start the application
if __name__ == '__main__':
    app.run_server(debug=True)
[(26, 'tensorflow.keras.models.load_model', 'tf.keras.models.load_model', (['PATH_MODEL'], {}), True, 'import tensorflow as tf\n'), (49, 'dash.Dash', 'dash.Dash', (['"""Traffic Signs Recognition"""'], {'external_stylesheets': '[dbc.themes.BOOTSTRAP]'}), False, 'import dash\n'), (18, 'yaml.safe_load', 'yaml.safe_load', (['yaml_data'], {}), False, 'import yaml\n'), (43, 'numpy.array', 'np.array', (['image'], {}), True, 'import numpy as np\n'), (130, 'dash.dependencies.Output', 'Output', (['"""mon-image"""', '"""children"""'], {}), False, 'from dash.dependencies import Input, Output\n'), (179, 'dash.dependencies.Output', 'Output', ([], {'component_id': '"""ma-zone-resultat"""', 'component_property': '"""children"""'}), False, 'from dash.dependencies import Input, Output\n'), (46, 'numpy.array', 'np.array', (['images_list'], {}), True, 'import numpy as np\n'), (105, 'dash_html_components.Div', 'html.Div', (['[navbar]'], {}), True, 'import dash_html_components as html\n'), (107, 'dash_html_components.Div', 'html.Div', (['cards'], {}), True, 'import dash_html_components as html\n'), (126, 'dash_html_components.Div', 'html.Div', ([], {'id': '"""mon-image"""'}), True, 'import dash_html_components as html\n'), (127, 'dash_html_components.Div', 'html.Div', ([], {'id': '"""ma-zone-resultat"""'}), True, 'import dash_html_components as html\n'), (131, 'dash.dependencies.Input', 'Input', (['"""bouton-chargement"""', '"""contents"""'], {}), False, 'from dash.dependencies import Input, Output\n'), (180, 'dash.dependencies.Input', 'Input', ([], {'component_id': '"""mon-champ-texte"""', 'component_property': '"""value"""'}), False, 'from dash.dependencies import Input, Output\n'), (152, 'io.BytesIO', 'io.BytesIO', ([], {}), False, 'import io\n'), (85, 'dash_html_components.H5', 'html.H5', (['"""Présentation"""'], {'className': '"""card-title"""'}), True, 'import dash_html_components as html\n'), (86, 'dash_html_components.P', 'html.P', (['["Cette application à pour but de réaliser des modèles capables de classer des panneaux de signalisation allemand à partir d\'une image. L\'application fonctionne de la manière suivante : vous déposer une image à l\'emplacement indiqué et la prédiction du modèle apparait immédiatement en dessous. En haut à droite vous pouvez sélectionner le modèle que vous voulez tester."\n    ]'], {'className': '"""card-text"""'}), True, 'import dash_html_components as html\n'), (136, 'base64.b64decode', 'base64.b64decode', (['content_string'], {}), False, 'import base64\n'), (139, 'dash_html_components.Hr', 'html.Hr', ([], {'style': "{'margin-top': '75px'}"}), True, 'import dash_html_components as html\n'), (140, 'dash_html_components.Img', 'html.Img', ([], {'src': 'contents', 'style': "{'margin-left': '750px'}"}), True, 'import dash_html_components as html\n'), (142, 'dash_html_components.Hr', 'html.Hr', ([], {}), True, 'import dash_html_components as html\n'), (66, 'dash_bootstrap_components.DropdownMenuItem', 'dbc.DropdownMenuItem', (['"""Réseau de Neurones"""'], {'header': '(True)'}), True, 'import dash_bootstrap_components as dbc\n'), (67, 'dash_bootstrap_components.DropdownMenuItem', 'dbc.DropdownMenuItem', (['"""SVM"""'], {'href': '"""#"""'}), True, 'import dash_bootstrap_components as dbc\n'), (112, 'dash_html_components.A', 'html.A', (['"""sélectionner une image"""'], {}), True, 'import dash_html_components as html\n'), (150, 'base64.b64decode', 'base64.b64decode', (['content_string'], {}), False, 'import base64\n'), (157, 'base64.b64encode', 'base64.b64encode', (['img_bytes'], {}), False, 'import base64\n'), (162, 'dash_html_components.Hr', 'html.Hr', ([], {'style': "{'margin-top': '75px'}"}), True, 'import dash_html_components as html\n'), (163, 'dash_html_components.Img', 'html.Img', ([], {'src': "('data:image/png;base64,' + content_string)", 'style': "{'margin-left': '750px'}"}), True, 'import dash_html_components as html\n'), (165, 'dash_html_components.Hr', 'html.Hr', ([], {}), True, 'import dash_html_components as html\n'), (169, 'dash_html_components.Hr', 'html.Hr', ([], {}), True, 'import dash_html_components as html\n'), (171, 'dash_html_components.Hr', 'html.Hr', ([], {}), True, 'import dash_html_components as html\n'), (172, 'dash_html_components.Div', 'html.Div', (['"""Raw Content"""'], {}), True, 'import dash_html_components as html\n'), (173, 'dash_html_components.Pre', 'html.Pre', (['contents'], {'style': 'pre_style'}), True, 'import dash_html_components as html\n')]