<?php

declare(strict_types=1);

namespace Intervention\Gif;

use Exception;
use Intervention\Gif\Blocks\FrameBlock;
use Intervention\Gif\Blocks\GraphicControlExtension;
use Intervention\Gif\Blocks\ImageDescriptor;
use Intervention\Gif\Blocks\NetscapeApplicationExtension;
use Intervention\Gif\Blocks\TableBasedImage;
use Intervention\Gif\Exceptions\DecoderException;
use Intervention\Gif\Exceptions\EncoderException;
use Intervention\Gif\Traits\CanHandleFiles;

class Builder
{
    use CanHandleFiles;

    /**
     * Create new instance
     *
     * @param GifDataStream $gif
     * @return void
     */
    public function __construct(protected GifDataStream $gif = new GifDataStream())
    {
    }

    /**
     * Get GifDataStream object we're currently building
     *
     * @return GifDataStream
     */
    public function getGifDataStream(): GifDataStream
    {
        return $this->gif;
    }

    /**
     * Set canvas size of gif
     *
     * @param int $width
     * @param int $height
     * @return Builder
     */
    public function setSize(int $width, int $height): self
    {
        $this->gif->getLogicalScreenDescriptor()->setSize($width, $height);

        return $this;
    }

    /**
     * Create new canvas
     *
     * @param int $width
     * @param int $height
     * @return self
     */
    public static function canvas(int $width, int $height): self
    {
        return (new self())->setSize($width, $height);
    }

    /**
     * Set loop count
     *
     * @param int $loops
     * @return Builder
     * @throws Exception
     */
    public function setLoops(int $loops): self
    {
        if ($this->gif->getFrames() === []) {
            throw new Exception('Add at least one frame before setting the loop count');
        }

        if ($loops >= 0) {
            // add frame count to existing or new netscape extension on first frame
            if (!$this->gif->getFirstFrame()->getNetscapeExtension()) {
                $this->gif->getFirstFrame()->addApplicationExtension(
                    new NetscapeApplicationExtension()
                );
            }
            $this->gif->getFirstFrame()->getNetscapeExtension()->setLoops($loops);
        }

        return $this;
    }

    /**
     * Create new animation frame from given source
     * which can be path to a file or GIF image data
     *
     * @param string|resource $source
     * @param float $delay time delay in seconds
     * @param int $left position offset in pixels from left
     * @param int $top position offset in pixels from top
     * @param bool $interlaced
     * @throws DecoderException
     * @return Builder
     */
    public function addFrame(
        mixed $source,
        float $delay = 0,
        int $left = 0,
        int $top = 0,
        bool $interlaced = false
    ): self {
        $frame = new FrameBlock();
        $source = Decoder::decode($source);

        // store delay
        $frame->setGraphicControlExtension(
            $this->buildGraphicControlExtension(
                $source,
                intval($delay * 100)
            )
        );

        // store image
        $frame->setTableBasedImage(
            $this->buildTableBasedImage($source, $left, $top, $interlaced)
        );

        // add frame
        $this->gif->addFrame($frame);

        return $this;
    }

    /**
     * Build new graphic control extension with given delay & disposal method
     *
     * @param GifDataStream $source
     * @param int $delay
     * @param DisposalMethod $disposalMethod
     * @return GraphicControlExtension
     */
    protected function buildGraphicControlExtension(
        GifDataStream $source,
        int $delay,
        DisposalMethod $disposalMethod = DisposalMethod::BACKGROUND
    ): GraphicControlExtension {
        // create extension
        $extension = new GraphicControlExtension($delay, $disposalMethod);

        // set transparency index
        $control = $source->getFirstFrame()->getGraphicControlExtension();
        if ($control && $control->getTransparentColorExistance()) {
            $extension->setTransparentColorExistance();
            $extension->setTransparentColorIndex(
                $control->getTransparentColorIndex()
            );
        }

        return $extension;
    }

    /**
     * Build table based image object from given source
     *
     * @param GifDataStream $source
     * @param int $left
     * @param int $top
     * @param bool $interlaced
     * @return TableBasedImage
     */
    protected function buildTableBasedImage(
        GifDataStream $source,
        int $left,
        int $top,
        bool $interlaced
    ): TableBasedImage {
        $block = new TableBasedImage();
        $block->setImageDescriptor(new ImageDescriptor());

        // set global color table from source as local color table
        $block->getImageDescriptor()->setLocalColorTableExistance();
        $block->setColorTable($source->getGlobalColorTable());

        $block->getImageDescriptor()->setLocalColorTableSorted(
            $source->getLogicalScreenDescriptor()->getGlobalColorTableSorted()
        );

        $block->getImageDescriptor()->setLocalColorTableSize(
            $source->getLogicalScreenDescriptor()->getGlobalColorTableSize()
        );

        $block->getImageDescriptor()->setSize(
            $source->getLogicalScreenDescriptor()->getWidth(),
            $source->getLogicalScreenDescriptor()->getHeight()
        );

        // set position
        $block->getImageDescriptor()->setPosition($left, $top);

        // set interlaced flag
        $block->getImageDescriptor()->setInterlaced($interlaced);

        // add image data from source
        $block->setImageData($source->getFirstFrame()->getImageData());

        return $block;
    }

    /**
     * Encode the current build
     *
     * @throws EncoderException
     * @return string
     */
    public function encode(): string
    {
        return $this->gif->encode();
    }
}
