package main

/**
* XXX: Ugly workaround for https://github.com/amitbet/vnc2video/issues/10. I've copied the file and build a
* X264ImageCustomEncoder. Once this is merged, we can drop the encoder.go file again.
*/

import (
	"errors"
	"fmt"
	vnc "github.com/amitbet/vnc2video"
	"github.com/amitbet/vnc2video/encoders"
	"github.com/sirupsen/logrus"
	"image"
	"image/color"
	"io"
	"os"
	"os/exec"
	"strconv"
)

func encodePPMforRGBA(w io.Writer, img *image.RGBA) error {
	maxvalue := 255
	size := img.Bounds()
	// write ppm header
	_, err := fmt.Fprintf(w, "P6\n%d %d\n%d\n", size.Dx(), size.Dy(), maxvalue)
	if err != nil {
		return err
	}

	if convImage == nil {
		convImage = make([]uint8, size.Dy()*size.Dx()*3)
	}

	rowCount := 0
	for i := 0; i < len(img.Pix); i++ {
		if (i % 4) != 3 {
			convImage[rowCount] = img.Pix[i]
			rowCount++
		}
	}

	if _, err := w.Write(convImage); err != nil {
		return err
	}

	return nil
}

func encodePPMGeneric(w io.Writer, img image.Image) error {
	maxvalue := 255
	size := img.Bounds()
	// write ppm header
	_, err := fmt.Fprintf(w, "P6\n%d %d\n%d\n", size.Dx(), size.Dy(), maxvalue)
	if err != nil {
		return err
	}

	// write the bitmap
	colModel := color.RGBAModel
	row := make([]uint8, size.Dx()*3)
	for y := size.Min.Y; y < size.Max.Y; y++ {
		i := 0
		for x := size.Min.X; x < size.Max.X; x++ {
			color := colModel.Convert(img.At(x, y)).(color.RGBA)
			row[i] = color.R
			row[i+1] = color.G
			row[i+2] = color.B
			i += 3
		}
		if _, err := w.Write(row); err != nil {
			return err
		}
	}
	return nil
}

var convImage []uint8

func encodePPM(w io.Writer, img image.Image) error {
	if img == nil {
		return errors.New("nil image")
	}
	img1, isRGBImage := img.(*vnc.RGBImage)
	img2, isRGBA := img.(*image.RGBA)
	if isRGBImage {
		return encodePPMforRGBImage(w, img1)
	} else if isRGBA {
		return encodePPMforRGBA(w, img2)
	}
	return encodePPMGeneric(w, img)
}
func encodePPMforRGBImage(w io.Writer, img *vnc.RGBImage) error {
	maxvalue := 255
	size := img.Bounds()
	// write ppm header
	_, err := fmt.Fprintf(w, "P6\n%d %d\n%d\n", size.Dx(), size.Dy(), maxvalue)
	if err != nil {
		return err
	}

	if _, err := w.Write(img.Pix); err != nil {
		return err
	}
	return nil
}

type X264ImageCustomEncoder struct {
	encoders.X264ImageEncoder
	FFMpegBinPath      string
	cmd                *exec.Cmd
	input              io.WriteCloser
	closed             bool
	Framerate          int
	ConstantRateFactor int
}

func (enc *X264ImageCustomEncoder) Init(videoFileName string) {
	if enc.Framerate == 0 {
		enc.Framerate = 12
	}
	cmd := exec.Command(enc.FFMpegBinPath,
		"-f", "image2pipe",
		"-vcodec", "ppm",
		"-r", strconv.Itoa(enc.Framerate),
		"-an", // no audio
		"-y",
		"-i", "-",
		"-vcodec", "libx264",
		"-preset", "veryfast",
		"-g", "250",
		"-crf", strconv.Itoa(enc.ConstantRateFactor),
		"-pix_fmt", "yuv420p",
		videoFileName,
	)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	encInput, err := cmd.StdinPipe()
	enc.input = encInput
	if err != nil {
		logrus.WithError(err).Error("can't get ffmpeg input pipe.")
	}
	enc.cmd = cmd
}
func (enc *X264ImageCustomEncoder) Run(videoFileName string) error {
	if _, err := os.Stat(enc.FFMpegBinPath); os.IsNotExist(err) {
		return err
	}

	enc.Init(videoFileName)
	logrus.Infof("launching binary: %v", enc.cmd)
	err := enc.cmd.Run()
	if err != nil {
		logrus.WithError(err).Errorf("error while launching ffmpeg: %v", enc.cmd.Args)
		return err
	}
	return nil
}
func (enc *X264ImageCustomEncoder) Encode(img image.Image) {
	if enc.input == nil || enc.closed {
		return
	}

	err := encodePPM(enc.input, img)
	if err != nil {
		logrus.WithError(err).Error("error while encoding image.")
	}
}

func (enc *X264ImageCustomEncoder) Close() {
	if enc.closed {
		return
	}
	enc.closed = true
	err := enc.input.Close()
	if err != nil {
		logrus.WithError(err).Error("could not close input.")
	}

}