from tkinter import * 
from tkinter.ttk import *
from tkinter import filedialog
from tkinter import messagebox

#sudo apt-get install libtiff5-dev libjpeg8-dev zlib1g-dev \
    #libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python-tk

from PIL import Image
from PIL import ImageTk# https://pillow.readthedocs.io/en/3.2.x/   libjpeg-dev sudo pip3 install pillow  sudo apt-get install libjpeg8-dev python3-pil.imagetk
from PIL.ExifTags import TAGS


import os.path
import pickle
import hashlib
import time
import random
import logging
import copy
import threading
import collections

import itertools

from math import ceil
from enum import Enum

from timeit import default_timer


from lipyc.panels.MenuBar import TopMenu
from lipyc.panels.LeftPanel import LeftPanel
from lipyc.panels.RightPanel import RightPanel
from lipyc.panels.MainPanel import MainPanel

from lipyc.Library import Library
from lipyc.Album import Album
from lipyc.File import File
from lipyc.config import *
from lipyc.autologin import *
from lipyc.similarity import find_similarities

#class Certificat(Enum):
    #add_album = 0


class Application(Tk, WorkflowStep):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
                
        self.library = None
        
        self.parents_album = [] #if we are in subalbums for insertion supression etc
        
        self.selected = set()
        self.selected_mod = False

        self.current = 0 #page number   
        self.current_pagination = 0#if there is differnet paginations
        self.sortname = "name"
        self.action = Action.pagination_albums

        self.io_threads=[]

        self.topMenu = TopMenu(self, self)
        self.config(menu=self.topMenu)
        
        self.mainPanel = MainPanel(self, self)
        self.mainPanel.grid(row=0, column=1)
        
        self.leftPanel = LeftPanel(self, self)
        self.leftPanel.grid(row=0, column=0)
        
        self.rightPanel = RightPanel(self, self)
        self.rightPanel.grid(row=0, column=2)
        
        self.register_events()
        self.register_ticks()
        self.protocol("WM_DELETE_WINDOW", self.clean)
        
        self.load()

        self.last_objs  = [[]]#last_objs per pagination
        self.last_k     = -1
        
        
    def load(self):
        files = ["general.data"]
        for filename in files:
            if not os.path.isfile( filename ) :
                return False
        
        with open( "general.data", 'rb') as f:
            general = pickle.load(f)
            files, albums = {}, {}
            
            if general:
                if general["library_location"] and os.path.isdir( general["library_location"] ):
                    self.library = Library(general["library_name"], general["library_location"])
                    files, albums = self.library.load()
                    self.rightPanel.actionPanel.set()
                    
                self.parents_album.clear() 
                if general["parents_album"]:
                    for id_album in general["parents_album"].split('|'):
                        self.parents_album.append(albums[int(id_album)])
                #self.parents_album = general["parents_album"]
                self.current = general["current"]
                self.action = general["action"]
                
                self.last_objs = []
                if general["last_objs"]:
                    for data in general["last_objs"].split('|'):
                        tmp=[]
                        for row in data.split(';'):
                            id_obj, isalbum = row.split('#')
                            
                            if isalbum == 'True':
                                tmp.append(albums[int(id_obj)])
                            else:
                                tmp.append(files[int(id_obj)])
                        self.last_objs.append( tmp )
                
        
        self.refresh()
        
    def store(self):
        general={
            'library_name' : self.library.name if self.library else '',
            'library_location' : self.library.location if self.library else '',
            'parents_album' : '|'.join([str(alb.id) for alb in self.parents_album]),
            'current' : self.current,
            'action' : self.action,
            'last_objs': '|'.join([ ';'.join( [str(obj.id)+'#'+str(isinstance(obj, Album)) for obj in objs] ) 
                for objs in self.last_objs]),
        }
        
        with open("general.data", 'wb') as f:
            pickle.dump(general, f, pickle.HIGHEST_PROTOCOL)
      
    def clean(self):
        self.save()
        for th in self.io_threads:
            if th.is_alive():
                th.join()
                
        self.destroy()
#### Async IO
    def save(self):
        self.io_threads = [ th for th in self.io_threads if th.is_alive ]                
        self.save_library()
        self.store()
    
    def save_library(self):
        if self.library :
            t = threading.Thread(None, (lambda lib : lib.store()), None, (copy.deepcopy(self.library),) )#snapshot
            t.start()
            self.io_threads.append(t)

    def register_events(self):
        def escape(e):
            if self.parents_album :
                self.back()
      
        def enter(e):
            if len(self.selected) == 1:
                tmp = self.selected.pop()
                self.selected.add( tmp )
                
                if isinstance(tmp, File):
                    self.parents_album.append(self.parents_album[-1])
                    self.display_file(tmp, self.last_k)
                else:
                    self.action = Action.pagination
                    self.parents_album.append( tmp )

                self.refresh()
        
        def delete(e):
            self.remove( self.selected )
        
        def up(e):
            if self.action in [Action.pagination, Action.pagination_albums, Action.pagination_files, Action.pagination_similarities]:
                self.select_up()
        
        def down(e):
            if self.action in [Action.pagination, Action.pagination_albums, Action.pagination_files, Action.pagination_similarities]:
                self.select_down()
        
        def left(e):
            if self.action == Action.display_file:
                self.display_previous()
            elif self.action in [Action.pagination, Action.pagination_albums, Action.pagination_files, Action.pagination_similarities]:
                self.select_previous()
        
        def right(e):
            if self.action == Action.display_file:
                self.display_next()
            elif self.action in [Action.pagination, Action.pagination_albums, Action.pagination_files, Action.pagination_similarities]:
                self.select_next()
        
        def control_press(e):
            self.selected_mod = True
            
        def control_release(e):
            self.selected_mod = False
            
        self.bind("<Escape>", escape)
        self.bind("<Return>", enter)
        self.bind("<Delete>", delete)
        
        self.bind("<KeyPress-Control_L>", control_press)
        self.bind("<KeyPress-Control_R>", control_press)
        self.bind("<KeyRelease-Control_L>", control_release)
        self.bind("<KeyRelease-Control_R>", control_release)
        
        self.bind("<Up>", up)
        self.bind("<Down>", down)
        self.bind("<Left>", left)
        self.bind("<Right>", right)
        #http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/key-names.html
        #https://stackoverflow.com/questions/16082243/how-to-bind-ctrl-in-python-tkinter
        
    def register_ticks(self):
        def _save():
            self.after(300000, _save)#ms, each 5 minutes
            self.save()
        
        def _refresh():
            self.refresh(auto=True)
            self.after(1000, _refresh) 
        
        self.after(300000, _save)
        self.after(1000, _refresh)
       
    def select(self, obj, k):
        if not self.selected_mod :
            self.selected.clear()
        
        self.last_k = k
        
        self.selected.add(obj)
        self.refresh()
    
    def select_previous(self):
        if self.last_k>-1:
            i = (self.last_k-1)%len(self.get_last_objs(self.current_pagination))
            self.select( self.get_last_objs(self.current_pagination)[i], i)
        
    def select_next(self):
        if self.last_k>-1:
            i = (self.last_k+1)%len(self.get_last_objs(self.current_pagination))
            self.select( self.get_last_objs(self.current_pagination)[i], i)
    
    def select_up(self):
        if self.last_k>-1:
            i = (self.last_k-self.mainPanel.num_x)%len(self.get_last_objs(self.current_pagination))
            self.select( self.get_last_objs(self.current_pagination)[i], i)
            
    def select_down(self):
        if self.last_k>-1:
            i = (self.last_k+self.mainPanel.num_x)%len(self.get_last_objs(self.current_pagination))
            self.select( self.get_last_objs(self.current_pagination)[i], i)

    def get_last_objs(self, id_pagination):
        return self.last_objs[id_pagination]
    
    def set_last_objs(self, id_pagination, objs):
        for k in range(len(self.last_objs), max(len(self.last_objs), id_pagination+1)):
            self.last_objs.append( [] )

        self.last_objs[id_pagination] = list(objs)
        

#### Begin Views
    def display_albums(self, albums):
        self.action = Action.pagination_albums

        self.set_last_objs(self.current_pagination, albums)
        
        self.mainPanel.set_pagination(albums)
        self.leftPanel.set_pagination(albums)
        self.rightPanel.set_pagination(self.selected)
        
        self.leftPanel.grid(row=0, column=0)
        self.mainPanel.grid(row=0, column=1)
        self.rightPanel.grid(row=0, column=2)
        
    def display_files(self, files):
        self.action = Action.pagination_files
        
        self.set_last_objs(self.current_pagination, files)
        
        self.mainPanel.set_pagination(files)
        self.leftPanel.set_pagination(files)
        self.rightPanel.set_pagination(self.selected)
        
        self.leftPanel.grid(row=0, column=0)
        self.mainPanel.grid(row=0, column=1)
        self.rightPanel.grid(row=0, column=2)
    
    def display_similarities(self, similarities):
        self.action = Action.pagination_similarities
        
        self.set_last_objs(self.current_pagination, similarities)
        #self.last_objs = similarities

        self.mainPanel.set_similarities(similarities)
        self.leftPanel.set_pagination(similarities)
        self.rightPanel.set_pagination(similarities)
        
        self.leftPanel.grid(row=0, column=0)
        self.mainPanel.grid(row=0, column=1)
        self.rightPanel.grid(row=0, column=2)
        
        
    def display_file(self, _file, k):#position de files in self.last_objs
        self.action = Action.display_file
        self.last_k = k
        
        self.mainPanel.set_display(_file)
        self.leftPanel.set_display(_file)
        self.rightPanel.set_display(_file)
       
        self.leftPanel.grid(row=0, column=0)
        self.mainPanel.grid(row=0, column=1)
        self.rightPanel.grid(row=0, column=2)
    
    def display_previous(self):
        if self.last_k>-1:
            i = (self.last_k-1)%len(self.get_last_objs(self.current_pagination))
            self.display_file( self.get_last_objs(self.current_pagination)[i], i)
        
    def display_next(self):
        if self.last_k>-1:
            i = (self.last_k+1)%len(self.get_last_objs(self.current_pagination))
            self.display_file( self.get_last_objs(self.current_pagination)[i], i)
#### End Views               
     
## Begin Event Handling
#### Begin TopPanel
    def back(self):
        if self.parents_album:
            self.parents_album.pop()

        self.current = 0
        self.action = Action.pagination
        self.refresh()
          
    def add_album(self, name):
        if not name :
            messagebox.showerror("Error", "Invalid name for album name")

        self.library.ressource_counter +=1
        album = Album(self.library.ressource_counter, self.library.scheduler, name )
        if self.parents_album :
            self.parents_album[-1].add_subalbum( album )
        else:
            self.library.add_album( album )
               
        self.action = Action.pagination_albums
        self.rightPanel.actionPanel.set()
        self.refresh()

    def show_files(self, current=0):
        self.current = current
        self.action = Action.pagination_files
        if self.parents_album :
            self.display_files( self.parents_album[-1].files)
        else:
            self.display_files([])
            
    def show_similarities(self, current=0):
        self.current = current
        self.action = Action.pagination_similarities

        self.display_similarities( set(self.get_last_objs(self.current_pagination)) )
          
    def show_library(self):
        self.action = Action.pagination
        self.refresh()
            
    def show_albums(self, current=0):
        self.current = current
        self.action = Action.pagination_albums
        
        if self.parents_album :
            self.display_albums( self.parents_album[-1].subalbums)
        else:
            self.display_albums(self.library.albums)

#### Begin TopPanel



#### Begin Menu
    def set_library_location(self, location=None):
        self.save_library()
        
        if not location:
            location = filedialog.askdirectory()
        
        if not location:
            return False
            
        name = os.path.basename( location )
            
        if not os.path.isdir( location ):
            messagebox.showerror("Error", "Library location : invalid")
        
        self.library = Library( name, location )
        self.library.load()
        
        self.parents_album.clear()
        self.selected.clear()
        
        self.rightPanel.actionPanel.set()
        
        self.action = Action.pagination
        self.refresh()

    def import_directory(self):
        location = filedialog.askdirectory()

        if not self.library:
            messagebox.showerror("Error", "Library location : not defined")
            return False
            
        if not location :
            return False
        
        if not os.path.isdir( location ):
            messagebox.showerror("Error", "Source location : not defined")
            return False
        self.io_threads.append( self.library.add_directory( location, self.rightPanel.actionPanel.set ) )
          
    


    def export_to(self):
        location = filedialog.askdirectory()
        if not location :
            return False
            
        if not os.path.isdir( location ):
            messagebox.showerror("Error", "No target specified")
            return False
        
        if not self.selected:
            messagebox.showerror("Error", "Nothing to export")
            return False
    
        def _export_to(objs):
            for obj in objs:
                obj.export_to(location)

        th = threading.Thread(None, _export_to, None, [copy.deepcopy( self.selected )])
        th.start()
        self.io_threads.append(th)
        
    
#### End Menu
     
#### Begin RightPanel

###### Begin ActionPanel
    def add_to(self,  parent_album):
        for obj in self.selected:
            if isinstance(obj, Album):
                self.library.add_album_to(obj, parent_album)
            else:
                self.library.add_file_to(obj, parent_album)
                
    def copy_to(self, parent_album):
        for obj in self.selected:
            if isinstance(obj, Album):
                self.library.copy_album_to(obj, parent_album)
            else:
                self.library.copy_file_to(obj, parent_album)
        
    def move_to(self, parent_album):        
        for obj in list(self.selected):
            self.copy_to(parent_album)
            if isinstance(obj, Album):
                #parent_album.add_subalbum( obj  )
                self.remove_album( obj )
            else:
                #self.library.scheduler.add_file(obj)
                #parent_album.add_file(obj)
                self.remove_file(obj)
        
###### End ActionPanel

#### End RightPanel

#### Begin BottomPanel
    def next_page(self):
        self.current += 1
        self.refresh()
        
    def previous_page(self):
        self.current -= 1
        self.refresh()
        
    def next_pagination(self):
        self.current_pagination += 1
        self.refresh()
        
    def previous_pagination(self):
        self.current -= 1
        self.current_pagination()
      
#### End BottomPanel
## End Event Handling
    
    def rename_album(self, obj, name):
        if not name :
            messagebox.showerror("Error", "Invalid name for album name")
            
        obj.rename( name )
        
        self.rightPanel.actionPanel.set()
        self.refresh()
        
    def set_cover(self, album):
        location = filedialog.askopenfilename()
        if not location :
            return None
        
        if not os.path.isfile( location ):
            messagebox.showerror("Error", "Invalid cover location")
        
        album.set_thumbnail(location)
        
    def set_cover_from(self, album, _file):
        if isinstance(album, Album):
            album.set_thumbnail( self.library.scheduler.get_file(_file.md5) )

    def find_similarities_inside(self):#permet de reduire la complexité, on fait du On² sur des partitions
        if not self.library:
            messagebox.showerror("Error", "No library openned") #on peutle faire avec un decorateur
            return
        
        
        if self.parents_album:
            files = self.parents_album[-1].deep_files()
        else:
            files = self.library.deep_files()
        raw_similarities = find_similarities(files)

        similarities0 = collections.defaultdict( set ) # on dit que ~s est transitive
        for (f1,f2) in raw_similarities:
            similarities0[ f1 ].add(f2)
            similarities0[ f2 ].add(f1)
            
        #maintenant on recupere une seul classe déquivalence
        similarities=[]
        history = set()
        
        for f, twins in similarities0.items():
            if not history.intersection( twins ) and not f in history:
                history.add(f)
                history.update(twins)
                
                twins.add( f )
                similarities.append( twins )

        if similarities:
            self.current = 0
            self.current_pagination = 0
            self.last_objs=[[]]
            for k,twins in enumerate(similarities):
                self.set_last_objs(k, twins)

            self.action = Action.pagination_similarities
            self.refresh()


    def set_sortname(self, name):
        self.sortname = name
        
    def _refresh(self):
        if not self.library:
            return None
        if self.action == Action.pagination:
            if self.parents_album and self.parents_album[-1].files:
                self.action = Action.pagination_files
            else:
                self.action = Action.pagination_albums
            
        if self.action == Action.pagination_files:
            self.show_files(self.current)
        elif self.action == Action.pagination_albums:
            self.show_albums(self.current)
        elif self.action == Action.pagination_similarities:
            self.show_similarities(self.current)
        elif self.action == Action.display_file:
            pass
        elif self.action == Action.configure_pgs:
            pass
            
    def refresh(self, auto=False):
        s = default_timer()
        if not auto :
            self.mainPanel.reset()
        self._refresh()
   
    def remove_file(self, _file, refresh=False):#surtout pas de thread io
        self.parents_album[-1].remove_file( _file )
        self.library.remove_files( [_file] )
        
        self.selected.discard(_file)
    
        if refresh:
            self.refresh()
            
    def remove_album(self, album, refresh=False):#surtout pas de thread io
        if self.parents_album:
            self.parents_album[-1].remove_subalbum( album )
        
        for alb in album.all_albums():
            self.library.remove_album( alb )
            self.selected.discard( alb )
           
        self.rightPanel.actionPanel.set()
        if refresh:
            self.refresh()

    
    def remove(self, objs):
        for obj in list(objs):
            if isinstance(obj, Album):
                self.remove_album(obj)
            else:
                self.remove_file(obj)
                
        self.refresh()

    def easy_configure_pgs(self):
        if not self.library:
            messagebox.showerror("Error", "No library openned")
            return
        self.action = Action.easy_configure_pgs
        
        self.rightPanel.hide()
        self.leftPanel.hide()
        
        self.mainPanel.set_easy_scheduler()
        self.mainPanel.grid(row=0, column=1)
        
    def configure_pgs(self):
        if not self.library:
            messagebox.showerror("Error", "No library openned")
            return
        self.action = Action.configure_pgs
        
        self.rightPanel.hide()
        self.leftPanel.hide()
        
        self.mainPanel.set_scheduler()
        self.mainPanel.grid(row=0, column=1)
        
    def save_pgs(self, data):
        if not self.library:
            messagebox.showerror("Error", "No library openned")
            return 

        with open("pgs.json", "w") as fp:
            fp.write(data)
            
        self.library.scheduler.parse()
    
    def quick_restore_files(self):
        if not self.library:
            messagebox.showerror("Error", "No library openned")
            return 
            
        for th in self.io_threads:
            if th.is_alive():
                th.join()
        
        self.library.scheduler.quick_restore()
    
    def reset_storage(self):
        for th in self.io_threads:
            if th.is_alive():
                th.join()
                
        if os.path.exists('general.data'):
            os.remove('general.data')

        if self.library:
            self.library.reset_storage()
            
        self.destroy()
