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', True, 'import tensorflow as tf\n'), (49, 'dash.Dash', 'dash.Dash', False, 'import dash\n'), (18, 'yaml.safe_load', 'yaml.safe_load', False, 'import yaml\n'), (43, 'numpy.array', 'np.array', True, 'import numpy as np\n'), (130, 'dash.dependencies.Output', 'Output', False, 'from dash.dependencies import Input, Output\n'), (179, 'dash.dependencies.Output', 'Output', False, 'from dash.dependencies import Input, Output\n'), (46, 'numpy.array', 'np.array', True, 'import numpy as np\n'), (105, 'dash_html_components.Div', 'html.Div', True, 'import dash_html_components as html\n'), (107, 'dash_html_components.Div', 'html.Div', True, 'import dash_html_components as html\n'), (126, 'dash_html_components.Div', 'html.Div', True, 'import dash_html_components as html\n'), (127, 'dash_html_components.Div', 'html.Div', True, 'import dash_html_components as html\n'), (131, 'dash.dependencies.Input', 'Input', False, 'from dash.dependencies import Input, Output\n'), (180, 'dash.dependencies.Input', 'Input', False, 'from dash.dependencies import Input, Output\n'), (152, 'io.BytesIO', 'io.BytesIO', False, 'import io\n'), (85, 'dash_html_components.H5', 'html.H5', True, 'import dash_html_components as html\n'), (86, 'dash_html_components.P', 'html.P', True, 'import dash_html_components as html\n'), (136, 'base64.b64decode', 'base64.b64decode', False, 'import base64\n'), (139, 'dash_html_components.Hr', 'html.Hr', True, 'import dash_html_components as html\n'), (140, 'dash_html_components.Img', 'html.Img', 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', True, 'import dash_bootstrap_components as dbc\n'), (67, 'dash_bootstrap_components.DropdownMenuItem', 'dbc.DropdownMenuItem', True, 'import dash_bootstrap_components as dbc\n'), (112, 'dash_html_components.A', 'html.A', True, 'import dash_html_components as html\n'), (150, 'base64.b64decode', 'base64.b64decode', False, 'import base64\n'), (157, 'base64.b64encode', 'base64.b64encode', False, 'import base64\n'), (162, 'dash_html_components.Hr', 'html.Hr', True, 'import dash_html_components as html\n'), (163, 'dash_html_components.Img', 'html.Img', 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', True, 'import dash_html_components as html\n'), (173, 'dash_html_components.Pre', 'html.Pre', True, 'import dash_html_components as html\n')]