#!/usr/bin/env python3
"""
Dependencies:
    pip install PyQt5 pillow
Run:
    python resize_images.py
"""

import sys, os
from PIL import Image
from PyQt5.QtWidgets import (
    QApplication, QWidget, QListWidget, QListWidgetItem, QLabel,
    QLineEdit, QPushButton, QComboBox, QFileDialog, QHBoxLayout,
    QVBoxLayout, QMessageBox
)
from PyQt5.QtGui import QPixmap, QIcon
from PyQt5.QtCore import Qt

# Custom QListWidget that accepts drag‐and‐drop of files and lets the user delete entries.
class DropListWidget(QListWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setAcceptDrops(True)
        self.setSelectionMode(QListWidget.ExtendedSelection)

    def dragEnterEvent(self, event):
        if event.mimeData().hasUrls():
            event.acceptProposedAction()
        else:
            event.ignore()

    def dragMoveEvent(self, event):
        if event.mimeData().hasUrls():
            event.acceptProposedAction()
        else:
            event.ignore()

    def dropEvent(self, event):
        for url in event.mimeData().urls():
            path = url.toLocalFile()
            if os.path.isfile(path) and self._is_image(path):
                self._add_item(path)
        event.acceptProposedAction()

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Delete:
            for item in self.selectedItems():
                self.takeItem(self.row(item))
        else:
            super().keyPressEvent(event)

    def _is_image(self, path):
        ext = os.path.splitext(path)[1].lower()
        return ext in {'.png', '.jpg', '.jpeg', '.bmp', '.gif', '.tiff', '.webp'}

    def _add_item(self, path):
        # Avoid duplicates
        for i in range(self.count()):
            if self.item(i).data(Qt.UserRole) == path:
                return
        img = QPixmap(path)
        icon = QIcon(img.scaled(64, 64, Qt.KeepAspectRatio, Qt.SmoothTransformation))
        item = QListWidgetItem(icon, os.path.basename(path))
        item.setData(Qt.UserRole, path)
        self.addItem(item)


class ResizerApp(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Batch Image Resizer")
        self._build_ui()
        self._map_methods()

    def _build_ui(self):
        # Drag‐and‐drop area
        self.list_widget = DropListWidget()
        self.list_widget.setFixedHeight(200)
        label_drop = QLabel("Drag & drop images below:")
        
        # Scale factor input
        lbl_scale = QLabel("Scale factor:")
        self.input_scale = QLineEdit("1.0")
        self.input_scale.setFixedWidth(60)

        # Resampling method chooser
        lbl_method = QLabel("Method:")
        self.combo_method = QComboBox()
        self.combo_method.addItems(["Bicubic", "Bilinear", "Nearest-Neighbor", "Lanczos"])

        # Output directory chooser
        lbl_out = QLabel("Output folder:")
        self.input_out = QLineEdit()
        btn_browse = QPushButton("Browse")
        btn_browse.clicked.connect(self._browse_folder)

        # GO button & progress label
        self.btn_go = QPushButton("GO")
        self.btn_go.clicked.connect(self._process_images)
        self.lbl_progress = QLabel("Waiting...")

        # Layout setup
        row_scale = QHBoxLayout()
        row_scale.addWidget(lbl_scale)
        row_scale.addWidget(self.input_scale)
        row_scale.addSpacing(20)
        row_scale.addWidget(lbl_method)
        row_scale.addWidget(self.combo_method)
        row_scale.addStretch()

        row_out = QHBoxLayout()
        row_out.addWidget(lbl_out)
        row_out.addWidget(self.input_out)
        row_out.addWidget(btn_browse)

        row_go = QHBoxLayout()
        row_go.addWidget(self.btn_go)
        row_go.addWidget(self.lbl_progress)
        row_go.addStretch()

        main_layout = QVBoxLayout()
        main_layout.addWidget(label_drop)
        main_layout.addWidget(self.list_widget)
        main_layout.addLayout(row_scale)
        main_layout.addLayout(row_out)
        main_layout.addLayout(row_go)

        self.setLayout(main_layout)

    def _map_methods(self):
        # Map combo box text to PIL resample filters
        self.methods = {
            "Bicubic": Image.BICUBIC,
            "Bilinear": Image.BILINEAR,
            "Nearest-Neighbor": Image.NEAREST,
            "Lanczos": Image.LANCZOS
        }

    def _browse_folder(self):
        folder = QFileDialog.getExistingDirectory(self, "Select Output Folder", "")
        if folder:
            self.input_out.setText(folder)

    def _process_images(self):
        try:
            scale = float(self.input_scale.text())
            if scale <= 0:
                raise ValueError
        except ValueError:
            QMessageBox.warning(self, "Invalid Scale", "Enter a positive number for scale.")
            return

        out_dir = self.input_out.text().strip()
        if not out_dir:
            QMessageBox.warning(self, "No Output Folder", "Please specify an output folder.")
            return
        if not os.path.isdir(out_dir):
            try:
                os.makedirs(out_dir)
            except Exception as e:
                QMessageBox.critical(self, "Error", f"Cannot create folder:\n{e}")
                return

        total = self.list_widget.count()
        if total == 0:
            QMessageBox.information(self, "Nothing to do", "No images in the list.")
            return

        method_name = self.combo_method.currentText()
        resample = self.methods[method_name]

        self.btn_go.setEnabled(False)
        done = 0

        while self.list_widget.count() > 0:
            item = self.list_widget.item(0)
            path = item.data(Qt.UserRole)
            base = os.path.basename(path)
            try:
                img = Image.open(path)
                orig_format = img.format  # Keep original format
                w, h = img.size
                new_size = (int(w * scale), int(h * scale))
                resized = img.resize(new_size, resample=resample)
                out_path = os.path.join(out_dir, base)
                # Ensure correct format
                if orig_format:
                    resized.save(out_path, format=orig_format)
                else:
                    resized.save(out_path)
                img.close()
            except Exception as e:
                QMessageBox.warning(self, "Error", f"Failed to process {base}:\n{e}")
            # remove from list and update counter
            self.list_widget.takeItem(0)
            done += 1
            self.lbl_progress.setText(f"Processed {done} of {total}")
            QApplication.processEvents()

        self.btn_go.setEnabled(True)
        self.lbl_progress.setText("Finished.")
        QMessageBox.information(self, "Done", "All images have been resized.")


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = ResizerApp()
    window.resize(600, 400)
    window.show()
    sys.exit(app.exec_())
