<?php
namespace boru\boruai\Tools;

use boru\boruai\Models\File;
use boru\boruai\BoruAI;

class PDFConverter {
    private $docId;
    private $fileName;
    private $outputFiles=[];
    private $outputInfo = [];
    private $fileIds = [];
    /** @var File[] */
    private $files = [];
    /** @var Page[] */
    private $pages = [];
    private $outputDir = "";
    private $maxFiles = 0;
    private $asWebP = true;
    public function __construct($fileName,$outputDir=null) {
        if($outputDir) {
            $this->outputDir = $outputDir;
        } else {
            $this->outputDir = __DIR__.'/../../output/';
        }
        if(!extension_loaded('imagick') || !class_exists('Imagick')) {
            throw new \Exception("Imagick extension not loaded",0);
        }
        $this->fileName = $fileName;
        if(!file_exists($this->fileName)) {
            throw new \Exception("File not found",0);
        }
        if(!is_readable($this->fileName)) {
            throw new \Exception("File not readable",0);
        }
        if(!is_writable($this->outputDir)) {
            throw new \Exception("Output directory not writable",0);
        }
        if(!file_exists($this->outputDir)) {
            mkdir($this->outputDir);
        }
    }
    public function docId($docId=null) {
        if($docId!==null) {
            $this->docId = $docId;
        }
        return $this->docId;
    }
    public function maxFiles($maxFiles=null) {
        if($maxFiles!==null) {
            $this->setMaxFiles($maxFiles);
        }
        return $this->maxFiles;
    }
    public function setMaxFiles($maxFiles) {
        if($maxFiles === null) {
            return;
        }
        if ($maxFiles === false || $maxFiles <= 0) {
            $maxFiles = 0;
        }
        $this->maxFiles = $maxFiles;
    }
    public function asWebp() {
        $this->asWebP = true;
    }
    public function asJpg() {
        $this->asWebP = false;
    }

    /**
     * Run the conversion
     * @param int $maxFiles Maximum number of files to output
     * @return Page[] List of Page objects
     */
    public function run($maxFiles=null) {
        $this->convert($maxFiles);
        $this->upload();
        return $this->pages;
    }

    /**
     * Convert the PDF to images
     * @param int $maxFiles Maximum number of files to output
     * @return array List of filenames of the converted files
     */
    public function convert($maxFiles=null) {
        BoruAI::printDebug("PDFConverter","Converting ".$this->fileName);
        $this->setMaxFiles($maxFiles);
        $this->outputFiles = [];
        $this->outputInfo = [];

        $im = new \Imagick($this->fileName);

        $noOfPagesInPDF = $im->getNumberImages(); 
        BoruAI::printDebug("PDFConverter","Number of pages in PDF: ".$noOfPagesInPDF);
        if ($noOfPagesInPDF) { 
            for ($i = 0; $i < $noOfPagesInPDF; $i++) {
                $outFile = $this->outputDir.($i+1).'-'.rand().'.'.($this->asWebP ? 'webp' : 'jpg');
                $url = $this->fileName.'['.$i.']';
                if($this->asWebP) {
                    //as webp
                    $image = new \Imagick();
                    $image->setResolution(300,300);
                    $image->readimage($url);
                    $image->setImageFormat('webp');
                    $image->setOption('webp:method', '6'); 
                    $image->setImageAlphaChannel(\imagick::ALPHACHANNEL_ACTIVATE);
                    $image->setBackgroundColor(new \ImagickPixel('#ffffff'));
                    $image = $image->mergeImageLayers(\Imagick::LAYERMETHOD_FLATTEN);
                    $image->writeImage($outFile);
                } else {
                    //as jpg
                    $image = new \Imagick();
                    $image->setResolution(300,300);
                    $image->readimage($url);
                    $image->setImageFormat('jpg');
                    $image->setImageAlphaChannel(\imagick::ALPHACHANNEL_ACTIVATE);
                    $image->setBackgroundColor(new \ImagickPixel('#ffffff'));
                    $image->writeImage($outFile);
                }
                $this->outputFiles[$i] = $outFile;
                $info = [
                    "width"=>$image->getImageWidth(),
                    "height"=>$image->getImageHeight(),
                ];
                $this->outputInfo[$i] = $info;
            } 
        }
        if(count($this->outputFiles) > 0 && $this->maxFiles > 0) {
            $this->mergeFiles($this->maxFiles);
        }
        return $this->outputFiles;
    }

    /**
     * Merge the output files
     * @param int $maxFiles Maximum number of files to output
     * @return array List of filenames of the merged files
     */
    public function mergeFiles($maxFiles=8) {
        if(count($this->outputFiles) == 0) {
            $this->convert();
        }
        $outputFiles = [];
        $outputInfo = [];

        if(count($this->outputFiles) <= $maxFiles) {
            BoruAI::printDebug("PDFConverter","No need to merge");
            //echo "No need to merge\n";
            return $this->outputFiles;
        }
        BoruAI::printDebug("PDFConverter","Merging files.. have",count($this->outputFiles),"files.. need to reduce to",$maxFiles);
        //We will put the two images side by side.. reducing the number by 50% on each iteration.
        $firstFile = "";
        $firstI = 0;
        foreach($this->outputFiles as $i=>$file) {
            //on each even iteration, we set the first file
            if($i % 2 == 0) {
                //echo "Setting first file to $file\n";
                $firstFile = $file;
                $firstI = $i;
                continue;
            }

            $newImage = $this->doMerge($firstFile,$file);
            $outFile = $this->outputDir.($i+1).'-c-'.rand().'.webp';
            $newImage->writeImage($outFile);
            $outputFiles[] = $outFile;
            $info = [
                "width"=>$newImage->getImageWidth(),
                "height"=>$newImage->getImageHeight(),
            ];
            $outputInfo[] = $info;
            unlink($firstFile);
            unlink($file);
            unset($this->outputFiles[$firstI]);
            unset($this->outputFiles[$i]);
        }
        if(count($outputFiles) > 0) {
            if(!empty($this->outputFiles)) {
                foreach($this->outputFiles as $i=>$file) {
                    $outputFiles[] = $file;
                    $outputInfo[] = $this->outputInfo[$i];
                }
            }
            $this->outputFiles = $outputFiles;
            $this->outputInfo = $outputInfo;
            if(count($outputFiles) > $maxFiles) {
                $this->mergeFiles($maxFiles);
            }
        }
        return $this->outputFiles;
    }

    /**
     * Upload the output files
     * @param string $purpose Purpose of the files
     * @param bool $keepFiles Keep the files after upload
     * @return File[] List of File objects
     */
    public function upload($purpose="vision",$keepFiles=false) {
        if(!$purpose) {
            $purpose = "vision";
        }
        if(count($this->outputFiles) == 0) {
            $this->convert();
        }
        $fileIds = [];
        $files = [];
        $pages = [];
        BoruAI::printDebug("PDFConverter","Uploading files..");
        foreach($this->outputFiles as $file) {
            if(!file_exists($file)) {
                throw new \Exception("File not found",0);
            }
            BoruAI::printDebug("PDFConverter","Uploading file: ".$file);
            $fileObject = File::uploadFile($file,$purpose);
            if(is_object($fileObject)) {
                $fileIds[] = $fileObject->id();
                $files[] = $fileObject;
            } else {
                $fileIds[] = $fileObject;
            }
            if(!$keepFiles) {
                unlink($file);
            }
            BoruAI::printDebug("PDFConverter","Uploaded file: ".$fileObject->id());
        }
        $this->pages = $pages;
        $this->files = $files;
        $this->fileIds = $fileIds;
        BoruAI::printDebug("PDFConverter","Uploaded files.. have",count($this->fileIds),"file ids");
        return $this->files;
    }

    /**
     * Get the output files
     * @return File[] List of output files
     */
    public function getFiles() {
        return $this->files;
    }

    /**
     * Get the output file ids
     * @return array List of output file ids
     */
    public function getFileIds() {
        return $this->fileIds;
    }

    /**
     * Get the output info
     * @return array List of output info
     */
    public function getOutputFiles() {
        return $this->outputFiles;
    }


    private function doMerge($firstFile,$secondFile) {
        $image1 = new \Imagick($firstFile);
        $image2 = new \Imagick($secondFile);

        $newImage = new \Imagick();
        $newImage->newImage($image1->getImageWidth() + $image2->getImageWidth(), max($image1->getImageHeight(),$image2->getImageHeight()), new \ImagickPixel('transparent'), 'webp');
        $newImage->compositeImage($image1->getImage(), \Imagick::COMPOSITE_COPY, 0, 0);
        $newImage->compositeImage($image2->getImage(), \Imagick::COMPOSITE_COPY, $image1->getImageWidth(), 0);
        return $newImage;
    }

    /**
     * Convert a PDF file to images and upload them to OpenAI. Optionally merge the images into $maxFiles files.
     * @param string $fileName PDF file name
     * @param string $outputDir Output directory
     * @param int $maxFiles Maximum number of files to output
     * @return File[] List of output files
     */
    public static function convertPDF($fileName,$outputDir=null,$docId=null,$maxFiles=null) {
        $converter = new PDFConverter($fileName,$outputDir);
        $converter->docId($docId);
        return $converter->run($maxFiles);
    }
}