#!/usr/pkg/bin/python3.10

# Audio Tools, a module and set of tools for manipulating audio data
# Copyright (C) 2007-2015  Brian Langenberger

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

from __future__ import print_function
import sys
import os
import os.path
from io import BytesIO
import audiotools
import audiotools.text as _


# returns the dimensions of image_surface scaled to match the
# display surface size, but without changing its aspect ratio
def image_size(display_surface, image_surface):
    display_ratio = float(display_surface[0]) / float(display_surface[1])

    image_ratio = float(image_surface[0]) / float(image_surface[1])

    if image_ratio > display_ratio:  # image wider than display, when scaled
        new_width = display_surface[0]
        new_height = image_surface[1] / (float(image_surface[0]) /
                                         float(display_surface[0]))
    else:                              # image taller than display, when scaled
        new_width = image_surface[0] / (float(image_surface[1]) /
                                        float(display_surface[1]))
        new_height = display_surface[1]

    (new_width, new_height) = map(int, (new_width, new_height))

    return (new_width, new_height)

try:
    import pygtk
    pygtk.require("2.0")
    import gtk
    import gtk.gdk

    class ImageWidget_Gtk(gtk.DrawingArea):
        def __init__(self):
            gtk.DrawingArea.__init__(self)
            self.full_pixbuf = None
            self.scaled_thumbnail = None
            self.connect("expose_event", self.expose_event)
            self.connect("configure_event", self.configure_event)
            self.widget_width = 0
            self.widget_height = 0

        def set_pixbuf(self, pixbuf):
            self.full_pixbuf = pixbuf
            self.scaled_thumbnail = None
            self.queue_draw()

        def clear(self):
            self.full_pixbuf = None
            self.queue_draw()

        def expose_event(self, widget, area):
            self.draw_image()

        def configure_event(self, widget, area):
            self.widget_width = area.width
            self.widget_height = area.height
            self.draw_image()

        def draw_image(self):
            if self.full_pixbuf is None:
                return

            (image_width,
             image_height) = image_size((self.widget_width,
                                         self.widget_height),
                                        (self.full_pixbuf.get_width(),
                                         self.full_pixbuf.get_height()))

            if (((self.scaled_thumbnail is None) or
                 (self.scaled_thumbnail.get_width() != image_width) or
                 (self.scaled_thumbnail.get_height() != image_height))):
                self.scaled_thumbnail = self.full_pixbuf.scale_simple(
                    image_width,
                    image_height,
                    gtk.gdk.INTERP_HYPER)

            self.window.draw_pixbuf(
                gc=self.get_style().fg_gc[gtk.STATE_NORMAL],
                pixbuf=self.scaled_thumbnail,
                src_x=0,
                src_y=0,
                dest_x=(self.widget_width - image_width) // 2,
                dest_y=(self.widget_height - image_height) // 2,
                width=image_width,
                height=image_height)

    class Coverview_Gtk:
        def __init__(self):
            self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
            self.window.connect("delete_event", self.delete_event)
            self.window.connect("destroy", self.destroy)

            table = gtk.Table(rows=2, columns=2, homogeneous=False)

            self.list = gtk.ListStore(str, gtk.gdk.Pixbuf,
                                      str, str, str, str)

            self.list_widget = gtk.TreeView(self.list)
            self.list_widget.append_column(
                gtk.TreeViewColumn(
                    None, gtk.CellRendererText(), text=0))
            self.list_widget.set_headers_visible(False)
            list_scroll = gtk.ScrolledWindow()
            list_scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
            list_scroll.add(self.list_widget)
            self.list_widget.connect("cursor-changed", self.image_selected)

            self.image = ImageWidget_Gtk()

            accelgroup = gtk.AccelGroup()
            self.window.add_accel_group(accelgroup)
            menubar = gtk.MenuBar()
            file_menu = gtk.Menu()
            file_item = gtk.MenuItem("File")
            file_item.set_submenu(file_menu)

            help_menu = gtk.Menu()
            help_item = gtk.MenuItem("Help")
            help_item.set_right_justified(True)
            help_item.set_submenu(help_menu)

            open_item = gtk.ImageMenuItem(gtk.STOCK_OPEN, accelgroup)
            open_item.connect("activate", self.open)
            file_menu.append(open_item)

            quit_item = gtk.ImageMenuItem(gtk.STOCK_QUIT, accelgroup)
            quit_item.connect("activate", self.destroy)
            file_menu.append(quit_item)

            about_item = gtk.ImageMenuItem(gtk.STOCK_ABOUT, accelgroup)
            about_item.connect("activate", self.about)
            help_menu.append(about_item)

            menubar.append(file_item)
            menubar.append(help_item)

            image_info_bar = gtk.HBox()
            self.size_bits = gtk.Label()
            self.size_pixels = gtk.Label()
            self.bits = gtk.Label()
            self.type = gtk.Label()

            self.size_bits.set_alignment(1.0, 0.0)
            self.size_pixels.set_alignment(1.0, 0.0)
            self.bits.set_alignment(1.0, 0.0)
            self.type.set_alignment(1.0, 0.0)

            image_info_bar.pack_start(self.size_bits)
            image_info_bar.pack_start(gtk.VSeparator(),
                                      expand=False, fill=False, padding=3)
            image_info_bar.pack_start(self.size_pixels)
            image_info_bar.pack_start(gtk.VSeparator(),
                                      expand=False, fill=False, padding=3)
            image_info_bar.pack_start(self.bits)
            image_info_bar.pack_start(gtk.VSeparator(),
                                      expand=False, fill=False, padding=3)
            image_info_bar.pack_start(self.type)

            self.statusbar = gtk.Statusbar()
            self.statusbar.push(0, "")

            table.attach(menubar, 0, 2, 0, 1, yoptions=0)
            table.attach(list_scroll, 0, 1, 1, 2, xoptions=0, xpadding=5)
            table.attach(self.image, 1, 2, 1, 2)
            table.attach(gtk.HSeparator(), 0, 2, 2, 3, yoptions=0)
            table.attach(image_info_bar, 0, 2, 3, 4, yoptions=0)
            table.attach(self.statusbar, 0, 2, 5, 6, yoptions=0)

            self.file_dialog = gtk.FileChooserDialog(
                title=_.LAB_CHOOSE_FILE,
                action=gtk.FILE_CHOOSER_ACTION_OPEN,
                buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                         gtk.STOCK_OPEN, gtk.RESPONSE_OK))

            self.about_dialog = gtk.AboutDialog()

            self.about_dialog.set_program_name(u"coverview")
            self.about_dialog.set_version(audiotools.VERSION)
            self.about_dialog.set_copyright(u"(c) Brian Langenberger")
            self.about_dialog.set_comments(_.LAB_COVERVIEW_ABOUT)
            self.about_dialog.set_website(_.LAB_AUDIOTOOLS_URL)

            self.window.add(table)
            self.window.set_default_size(800, 600)
            self.window.show_all()

        def delete_event(self, widget, event, data=None):
            return False

        def open(self, widget, data=None):
            response = self.file_dialog.run()
            if response == gtk.RESPONSE_OK:
                filename = self.file_dialog.get_filename()
                try:
                    self.set_metadata(
                        audiotools.open(filename).get_metadata())
                    self.set_filename(filename)
                except audiotools.UnsupportedFile:
                    error = gtk.MessageDialog(
                        type=gtk.MESSAGE_ERROR,
                        message_format=_.ERR_UNSUPPORTED_FILE % (filename),
                        buttons=gtk.BUTTONS_OK)
                    error.run()
                    error.destroy()
                except audiotools.InvalidFile:
                    error = gtk.MessageDialog(
                        type=gtk.MESSAGE_ERROR,
                        message_format=_.ERR_INVALID_FILE % (filename),
                        buttons=gtk.BUTTONS_OK)
                    error.run()
                    error.destroy()
                except IOError:
                    error = gtk.MessageDialog(
                        type=gtk.MESSAGE_ERROR,
                        message_format=_.ERR_OPEN_IOERROR % (filename),
                        buttons=gtk.BUTTONS_OK)
                    error.run()
                    error.destroy()
            elif response == gtk.RESPONSE_CANCEL:
                pass
            self.file_dialog.hide()

        def about(self, widget, data=None):
            self.about_dialog.run()
            self.about_dialog.hide()

        def destroy(self, widget, data=None):
            gtk.main_quit()

        def image_selected(self, treeview=None):
            (liststore,
             selection) = self.list_widget.get_selection().get_selected_rows()

            row = selection[0][0]
            treeiter = liststore.get_iter(row)

            self.image.set_pixbuf(liststore.get_value(treeiter, 1))
            self.size_bits.set_text(liststore.get_value(treeiter, 2))
            self.size_pixels.set_text(liststore.get_value(treeiter, 3))
            self.bits.set_text(liststore.get_value(treeiter, 4))
            self.type.set_text(liststore.get_value(treeiter, 5))

        def image_unselected(self):
            self.image.clear()
            self.size_bits.set_text(u"")
            self.size_pixels.set_text(u"")
            self.bits.set_text(u"")
            self.type.set_text(u"")

        def set_filename(self, path):
            self.statusbar.pop(0)
            self.statusbar.push(0, path)

        def set_metadata(self, metadata):
            def get_pixbuf(imagedata):
                l = gtk.gdk.PixbufLoader()
                l.write(imagedata)
                l.close()
                return l.get_pixbuf()

            self.list.clear()

            images = metadata.images()
            first_image = None
            for image in images:
                tree_iter = self.list.append()
                self.list.set(tree_iter, 0,
                              image.type_string())
                self.list.set(tree_iter, 1,
                              get_pixbuf(image.data))
                self.list.set(tree_iter, 2,
                              _.LAB_BYTE_SIZE % (len(image.data)))
                self.list.set(tree_iter, 3,
                              _.LAB_DIMENSIONS % (image.width, image.height))
                self.list.set(tree_iter, 4,
                              _.LAB_BITS_PER_PIXEL % (image.color_depth))
                self.list.set(tree_iter, 5, image.mime_type.decode('ascii'))

                if first_image is None:
                    first_image = tree_iter

            if len(images) > 0:
                self.list_widget.get_selection().select_iter(first_image)
                self.image_selected()
            else:
                self.image_unselected()

    def main_gtk(initial_audiofile=None):
        coverview = Coverview_Gtk()
        if initial_audiofile is not None:
            coverview.set_filename(initial_audiofile.filename)
            coverview.set_metadata(initial_audiofile.get_metadata())
        gtk.main()

    GTK_AVAILABLE = True
except ImportError:
    GTK_AVAILABLE = False

try:
    from Tkinter import *
    import Image
    import ImageTk
    import tkFileDialog
    import tkMessageBox

    try:
        import _imaging as test
    except ImportError as err:
        if os.uname()[0].lower() == 'darwin':
            print("""*** Unable to load Python Imaging Library.
You may need to run Python in 32-bit mode in order for it to load correctly.
If running a Bourne-like shell, try:

% export VERSIONER_PYTHON_PREFER_32_BIT=yes

or, if running a C-like shell, try:

% setenv VERSIONER_PYTHON_PREFER_32_BIT yes

Consult "man python" for further details.
""")
        raise err

    class ImagePair_Tk:
        def __init__(self, path, image_obj):
            """image_obj is an audiotools.Image object"""

            self.path = path
            self.audiotools = image_obj
            self.pil = Image.open(BytesIO(image_obj.data))

    class Statusbar_Tkinter(Frame):
        def __init__(self, master):
            Frame.__init__(self, master)

            self.image_info = Frame(self)
            self.path_info = Frame(self)

            self.path = Label(self.path_info, bd=1, relief=SUNKEN, anchor=W)
            self.path.pack(fill=X)

            self.size_bits = Label(self.image_info, bd=1, relief=SUNKEN,
                                   anchor=E)
            self.size_pixels = Label(self.image_info, bd=1, relief=SUNKEN,
                                     anchor=E)
            self.bits = Label(self.image_info, bd=1, relief=SUNKEN, anchor=E)
            self.type = Label(self.image_info, bd=1, relief=SUNKEN, anchor=E)
            self.size_bits.grid(row=0, column=0, sticky=E + W)
            self.size_pixels.grid(row=0, column=1, sticky=E + W)
            self.bits.grid(row=0, column=2, sticky=E + W)
            self.type.grid(row=0, column=3, sticky=E + W)
            self.image_info.columnconfigure(0, weight=1)
            self.image_info.columnconfigure(1, weight=1)
            self.image_info.columnconfigure(2, weight=1)
            self.image_info.columnconfigure(3, weight=1)

            self.image_info.pack(fill=X)
            self.path_info.pack(fill=X)

        def set_path(self, path):
            self.path.config(text=path)

        def set_image_data(self, image):
            if image is None:
                return
            else:
                at_image = image.audiotools
            self.path.config(text=image.path)
            self.size_bits.config(text=_.LAB_BYTE_SIZE % (len(at_image.data)))
            self.size_pixels.config(text=_.LAB_DIMENSIONS % (at_image.width,
                                                             at_image.height))
            self.bits.config(
                text=_.LAB_BITS_PER_PIXEL % (at_image.color_depth))
            self.type.config(text=at_image.mime_type)

        def clear_image_data(self):
            self.path.config(text="")
            self.size_bits.config(text="")
            self.size_pixels.config(text="")
            self.bits.config(text="")
            self.type.config(text="")

    class About_Tkinter(Frame):
        def __init__(self, master):
            Frame.__init__(self, master, padx=10, pady=10)

            Label(self, text="coverview",
                  font=("Helvetica", 48)).pack(side=TOP)
            Label(self,
                  text="Python Audio Tools %s" %
                  (audiotools.VERSION)).pack(side=TOP)
            Label(self,
                  text="(c) by Brian Langenberger",
                  font=("Helvetica", 10)).pack(side=TOP)
            Label(self,
                  text=_.LAB_COVERVIEW_ABOUT).pack(side=TOP)
            Label(self,
                  text=_.LAB_AUDIOTOOLS_URL).pack(side=TOP)

            close = Button(self, text=_.LAB_CLOSE)
            close.bind(sequence="<ButtonRelease-1>", func=self.close)
            close.pack(side=BOTTOM)

            self.master = master

        def close(self, event):
            self.master.withdraw()

    class Coverview_Tkinter:
        def __init__(self, master):
            self.master = master

            frame = Frame(master, width=800, height=600)

            self.statusbar = Statusbar_Tkinter(frame)
            self.statusbar.pack(side=BOTTOM, fill=X)

            self.images = Listbox(frame, selectmode=BROWSE)
            self.images.pack(fill=Y, expand=0, side=LEFT)
            self.images.bind(sequence="<ButtonRelease-1>",
                             func=self.event_selected)
            self.images.bind(sequence="<KeyRelease>",
                             func=self.event_selected)

            self.canvas = Canvas(frame)
            self.canvas.pack(fill=BOTH, expand=1, side=LEFT)
            self.canvas.bind(sequence="<Configure>", func=self.event_resized)

            self.photo_image_id = None
            self.current_image = None
            self.image_objects = []

            frame.pack(fill=BOTH, expand=1, side=LEFT)
            frame.master.title("coverview")
            frame.master.geometry("%dx%d" % (800, 600))

            self.image_width = int(self.canvas.cget("width"))
            self.image_height = int(self.canvas.cget("height"))

            self.menubar = Menu(master)
            file_menu = Menu(self.menubar, tearoff=0)
            file_menu.add_command(label="Open", command=self.open)
            file_menu.add_command(label="Quit", command=self.quit)
            self.menubar.add_cascade(label="File", menu=file_menu)

            help_menu = Menu(self.menubar, tearoff=0)
            help_menu.add_command(label="About", command=self.about)
            self.menubar.add_cascade(label="Help", menu=help_menu)

            # master.bind_all(sequence="<Control-o>", func=self.open)
            # master.bind_all(sequence="<Control-q>", func=self.quit)
            master.config(menu=self.menubar)

            self.about_window = Toplevel()
            self.about_window.withdraw()
            self.about_window_frame = About_Tkinter(self.about_window)
            self.about_window_frame.pack(fill=BOTH, expand=1)

        def open(self, *args):
            new_file = tkFileDialog.askopenfilename(
                parent=self.master,
                title=_.LAB_CHOOSE_FILE)

            if len(new_file) > 0:
                try:
                    self.set_metadata(new_file,
                                      audiotools.open(new_file).get_metadata())
                except audiotools.UnsupportedFile:
                    self.show_error(_.ERR_UNSUPPORTED_FILE % (new_file))
                except audiotools.InvalidFile:
                    self.show_error(_.ERR_INVALID_FILE % (new_file))
                except IOError as err:
                    self.show_error(_.ERR_OPEN_IOERROR % (new_file))

        def quit(self, *args):
            self.master.quit()

        def about(self, *args):
            self.about_window.state("normal")

        def show_error(self, error):
            if self.photo_image_id is not None:
                self.canvas.delete(self.photo_image_id)
            self.statusbar.clear_image_data()
            self.images.selection_clear(0, END)
            self.images.delete(0, END)
            self.image_objects = []
            self.statusbar.set_path(error)

        def event_selected(self, event):
            try:
                self.current_image = self.image_objects[
                    int(self.images.curselection()[0])]
                self.set_image(self.current_image)
            except IndexError:
                self.current_image = None
                self.set_image(None)

        def event_resized(self, event):
            self.image_width = event.width
            self.image_height = event.height
            self.set_image(self.current_image)

        def set_image(self, image):
            if image is None:
                if self.photo_image_id is not None:
                    self.canvas.delete(self.photo_image_id)
                self.statusbar.clear_image_data()
                return

            (image_width, image_height) = image_size(
                (self.image_width,
                 self.image_height),
                (self.current_image.pil.size[0],
                 self.current_image.pil.size[1]))

            resized_image = self.current_image.pil.resize((image_width,
                                                           image_height),
                                                          Image.ANTIALIAS)

            self.photo_image = ImageTk.PhotoImage(resized_image)

            if self.photo_image_id is not None:
                self.canvas.delete(self.photo_image_id)

            self.photo_image_id = self.canvas.create_image(
                (self.image_width - image_width) // 2,
                (self.image_height - image_height) // 2,
                image=self.photo_image,
                anchor=NW)

            self.statusbar.set_image_data(image)

        def set_metadata(self, path, metadata):
            self.images.selection_clear(0, END)
            self.images.delete(0, END)
            self.image_objects = []
            if metadata is not None:
                image_list = metadata.images()
                for image in image_list:
                    self.images.insert(END, image.type_string())
                    self.image_objects.append(ImagePair_Tk(path, image))

                if len(image_list) > 0:
                    self.images.index(0)
                    self.images.selection_set(0)
                    self.current_image = self.image_objects[0]
                    self.set_image(self.current_image)
                else:
                    self.set_image(None)
                    self.statusbar.set_path(path)
            else:
                self.set_image(None)
                self.statusbar.set_path(path)

    def main_tkinter(initial_audiofile=None):
        root = Tk()

        app = Coverview_Tkinter(root)
        if initial_audiofile is not None:
            app.set_metadata(initial_audiofile.filename,
                             initial_audiofile.get_metadata())

        root.mainloop()

    TKINTER_AVAILABLE = True
except ImportError:
    TKINTER_AVAILABLE = False

if (__name__ == '__main__'):
    import argparse

    parser = argparse.ArgumentParser(description=_.DESCRIPTION_COVERVIEW)

    parser.add_argument("--version",
                        action="version",
                        version="Python Audio Tools %s" % (audiotools.VERSION))

    if GTK_AVAILABLE:
        parser.add_argument('--no-gtk',
                            dest="no_gtk",
                            action='store_true',
                            default=False,
                            help=_.OPT_NO_GTK)

    if TKINTER_AVAILABLE:
        parser.add_argument('--no-tkinter',
                            dest="no_tkinter",
                            action='store_true',
                            default=False,
                            help=_.OPT_NO_TKINTER)

    parser.add_argument("filenames",
                        metavar="FILENAME",
                        nargs="*",
                        help=_.OPT_INPUT_FILENAME)

    options = parser.parse_args()

    args = options.filenames

    messenger = audiotools.Messenger()

    use_gtk = (hasattr(options, "no_gtk") and not options.no_gtk)
    use_tkinter = (hasattr(options, "no_tkinter") and not options.no_tkinter)

    if use_gtk:
        if len(args) > 0:
            try:
                main_gtk(audiotools.open(args[0]))
            except audiotools.UnsupportedFile:
                messenger.error(_.ERR_UNSUPPORTED_FILE %
                                (audiotools.Filename(args[0]),))
                sys.exit(1)
            except audiotools.InvalidFile:
                messenger.error(_.ERR_INVALID_FILE %
                                (audiotools.Filename(args[0]),))
                sys.exit(1)
            except IOError:
                messenger.error(_.ERR_OPEN_IOERROR %
                                (audiotools.Filename(args[0]),))
                sys.exit(1)
        else:
            main_gtk(None)
    elif use_tkinter:
        if len(args) > 0:
            try:
                main_tkinter(audiotools.open(args[0]))
            except audiotools.UnsupportedFile:
                messenger.error(_.ERR_UNSUPPORTED_FILE %
                                (audiotools.Filename(args[0]),))
                sys.exit(1)
            except audiotools.InvalidFile:
                messenger.error(_.ERR_INVALID_FILE %
                                (audiotools.Filename(args[0]),))
                sys.exit(1)
            except IOError:
                messenger.error(_.ERR_OPEN_IOERROR %
                                (audiotools.Filename(args[0]),))
                sys.exit(1)
        else:
            main_tkinter(None)
    else:
        messenger.error(_.ERR_NO_GUI)
        sys.exit(1)
