import collections
import cv2
import numpy
import os
import shutil
import struct

from qtpy import QtCore as QC
from qtpy import QtGui as QG
from qtpy import QtWidgets as QW

from hydrus.core import HydrusData
from hydrus.core import HydrusGlobals as HG
from hydrus.core import HydrusImageHandling
from hydrus.core import HydrusPaths
from hydrus.core import HydrusSerialisable

from hydrus.client import ClientConstants as CC
from hydrus.client.gui import ClientGUIFunctions
from hydrus.client.gui import QtPorting as QP

if cv2.__version__.startswith( '2' ):
    
    IMREAD_UNCHANGED = cv2.CV_LOAD_IMAGE_UNCHANGED
    
else:
    
    IMREAD_UNCHANGED = cv2.IMREAD_UNCHANGED
    

def CreateTopImage( width, title, payload_description, text ):
    
    text_extent_qt_image = HG.client_controller.bitmap_manager.GetQtImage( 20, 20, 24 )
    
    painter = QG.QPainter( text_extent_qt_image )
    
    text_font = QW.QApplication.font()
    
    basic_font_size = text_font.pointSize()
    
    payload_description_font = QW.QApplication.font()
    
    payload_description_font.setPointSize( int( basic_font_size * 1.4 ) )
    
    title_font = QW.QApplication.font()
    
    title_font.setPointSize( int( basic_font_size * 2.0 ) )
    
    texts_to_draw = []
    
    current_y = 6
    
    for ( t, f ) in ( ( title, title_font ), ( payload_description, payload_description_font ), ( text, text_font ) ):
        
        painter.setFont( f )
        
        wrapped_texts = WrapText( painter, t, width - 20 )
        line_height = painter.fontMetrics().height()
        
        wrapped_texts_with_ys = []
        
        if len( wrapped_texts ) > 0:
            
            current_y += 10
            
            for wrapped_text in wrapped_texts:
                
                wrapped_texts_with_ys.append( ( wrapped_text, current_y ) )
                
                current_y += line_height + 4
                
            
        
        texts_to_draw.append( ( wrapped_texts_with_ys, f ) )
        
    
    current_y += 6
    
    top_height = current_y
    
    del painter
    del text_extent_qt_image
    
    #
    
    top_qt_image = HG.client_controller.bitmap_manager.GetQtImage( width, top_height, 24 )
    
    painter = QG.QPainter( top_qt_image )
    
    painter.setBackground( QG.QBrush( QC.Qt.white ) )
    
    painter.eraseRect( painter.viewport() )
    
    #
    
    painter.drawPixmap( width-16-5, 5, CC.global_pixmaps().file_repository )
    
    #
    
    for ( wrapped_texts_with_ys, f ) in texts_to_draw:
        
        painter.setFont( f )
        
        for ( wrapped_text, y ) in wrapped_texts_with_ys:
            
            ( text_size, wrapped_text ) = ClientGUIFunctions.GetTextSizeFromPainter( painter, wrapped_text )
            
            ClientGUIFunctions.DrawText( painter, ( width - text_size.width() ) // 2, y, wrapped_text )
            
        
    
    del painter
    
    data_bytearray = top_qt_image.bits()
    
    if QP.qtpy.PYSIDE2:
        
        data_bytes = bytes( data_bytearray )
        
    elif QP.qtpy.PYQT5:
        
        data_bytes = data_bytearray.asstring( top_height * width * 3 )
        
    
    top_image_rgb = numpy.fromstring( data_bytes, dtype = 'uint8' ).reshape( ( top_height, width, 3 ) )
    
    top_image = cv2.cvtColor( top_image_rgb, cv2.COLOR_RGB2GRAY )
    
    top_height_header = struct.pack( '!H', top_height )
    
    byte0 = top_height_header[0:1]
    byte1 = top_height_header[1:2]
    
    top_image[0][0] = ord( byte0 )
    top_image[0][1] = ord( byte1 )
    
    return top_image
    
def DumpToPNG( width, payload_bytes, title, payload_description, text, path ):
    
    payload_bytes_length = len( payload_bytes )
    
    header_and_payload_bytes_length = payload_bytes_length + 4
    
    payload_height = int( header_and_payload_bytes_length / width )
    
    if ( header_and_payload_bytes_length / width ) % 1.0 > 0:
        
        payload_height += 1
        
    
    top_image = CreateTopImage( width, title, payload_description, text )
    
    payload_length_header = struct.pack( '!I', payload_bytes_length )
    
    num_empty_bytes = payload_height * width - header_and_payload_bytes_length
    
    header_and_payload_bytes = payload_length_header + payload_bytes + b'\x00' * num_empty_bytes
    
    payload_image = numpy.fromstring( header_and_payload_bytes, dtype = 'uint8' ).reshape( ( payload_height, width ) )
    
    finished_image = numpy.concatenate( ( top_image, payload_image ) )
    
    # this is to deal with unicode paths, which cv2 can't handle
    ( os_file_handle, temp_path ) = HydrusPaths.GetTempPath( suffix = '.png' )
    
    try:
        
        cv2.imwrite( temp_path, finished_image, [ cv2.IMWRITE_PNG_COMPRESSION, 9 ] )
        
        HydrusPaths.MirrorFile( temp_path, path )
        
    except Exception as e:
        
        HydrusData.ShowException( e )
        
        raise Exception( 'Could not save the png!' )
        
    finally:
        
        HydrusPaths.CleanUpTempPath( os_file_handle, temp_path )
        
    
def GetPayloadBytes( payload_obj ):
    
    if isinstance( payload_obj, bytes ):
        
        return payload_obj
        
    elif isinstance( payload_obj, str ):
        
        return bytes( payload_obj, 'utf-8' )
        
    else:
        
        return payload_obj.DumpToNetworkBytes()
        
    
def GetPayloadTypeString( payload_obj ):
    
    if isinstance( payload_obj, bytes ):
        
        return 'Bytes'
        
    elif isinstance( payload_obj, str ):
        
        return 'String'
        
    elif isinstance( payload_obj, HydrusSerialisable.SerialisableList ):
        
        type_string_counts = collections.Counter()
        
        for o in payload_obj:
            
            type_string_counts[ GetPayloadTypeString( o ) ] += 1
            
        
        type_string = ', '.join( ( HydrusData.ToHumanInt( count ) + ' ' + s for ( s, count ) in list(type_string_counts.items()) ) )
        
        return 'A list of ' + type_string
        
    elif isinstance( payload_obj, HydrusSerialisable.SerialisableBase ):
        
        return payload_obj.SERIALISABLE_NAME
        
    else:
        
        return repr( type( payload_obj ) )
        
    
def GetPayloadDescriptionAndBytes( payload_obj ):
    
    payload_bytes = GetPayloadBytes( payload_obj )
    
    payload_description = GetPayloadTypeString( payload_obj ) + ' - ' + HydrusData.ToHumanBytes( len( payload_bytes ) )
    
    return ( payload_description, payload_bytes )
    
def LoadFromPNG( path ):
    
    # this is to deal with unicode paths, which cv2 can't handle
    ( os_file_handle, temp_path ) = HydrusPaths.GetTempPath()
    
    try:
        
        HydrusPaths.MirrorFile( path, temp_path )
        
        try:
            
            numpy_image = cv2.imread( temp_path, flags = IMREAD_UNCHANGED )
            
            if numpy_image is None:
                
                raise Exception()
                
            
        except Exception as e:
            
            try:
                
                pil_image = HydrusImageHandling.GeneratePILImage( temp_path )
                
                numpy_image = HydrusImageHandling.GenerateNumPyImageFromPILImage( pil_image, dequantize = False )
                
            except Exception as e:
                
                HydrusData.ShowException( e )
                
                raise Exception( 'That did not appear to be a valid image!' )
                
            
        
    finally:
        
        HydrusPaths.CleanUpTempPath( os_file_handle, temp_path )
        
    
    try:
        
        height = numpy_image.shape[0]
        width = numpy_image.shape[1]
        
        if len( numpy_image.shape ) > 2:
            
            depth = numpy_image.shape[2]
            
            if depth != 1:
                
                raise Exception( 'The file did not appear to be monochrome!' )
                
            
        
        try:
            
            complete_data = numpy_image.tostring()
            
            top_height_header = complete_data[:2]
            
            ( top_height, ) = struct.unpack( '!H', top_height_header )
            
            payload_and_header_bytes = complete_data[ width * top_height : ]
            
        except:
            
            raise Exception( 'Header bytes were invalid!' )
            
        
        try:
            
            payload_length_header = payload_and_header_bytes[:4]
            
            ( payload_bytes_length, ) = struct.unpack( '!I', payload_length_header )
            
            payload_bytes = payload_and_header_bytes[ 4 : 4 + payload_bytes_length ]
            
        except:
            
            raise Exception( 'Payload bytes were invalid!' )
            
        
    except Exception as e:
        
        HydrusData.PrintException( e )
        
        message = 'The image loaded, but it did not seem to be a hydrus serialised png! The error was: {}'.format( str( e ) )
        message += os.linesep * 2
        message += 'If you believe this is a legit non-resized, non-converted hydrus serialised png, please send it to hydrus_dev.'
        
        raise Exception( message )
        
    
    return payload_bytes
    
def TextExceedsWidth( painter, text, width ):
    
    ( text_size, text ) = ClientGUIFunctions.GetTextSizeFromPainter( painter, text )
    
    return text_size.width() > width
    
def WrapText( painter, text, width ):
    
    words = text.split( ' ' )
    
    lines = []
    
    next_line = []
    
    for word in words:
        
        if word == '':
            
            continue
            
        
        potential_next_line = list( next_line )
        
        potential_next_line.append( word )
        
        if TextExceedsWidth( painter, ' '.join( potential_next_line ), width ):
            
            if len( potential_next_line ) == 1: # one very long word
                
                lines.append( ' '.join( potential_next_line ) )
                
                next_line = []
                
            else:
                
                lines.append( ' '.join( next_line ) )
                
                next_line = [ word ]
                
            
        else:
            
            next_line = potential_next_line
            
        
    
    if len( next_line ) > 0:
        
        lines.append( ' '.join( next_line ) )
        
    
    return lines
    
