/*
=====================================================================
ENKI — Visual Binary Transmutation Engine
=====================================================================
Author: R. Seaverns (K0NxT3D)
Version: 1.2
Year: 2025
Language: Go
Platform: Linux / Windows


Description:
ENKI is a visual encoding engine inspired by ancient symbolic
transmission systems. It transforms arbitrary binary data into
deterministic pixel glyphs, renders them as a spatial filmstrip,
and encodes the result into a modern MP4 container using lossless 
RGB encoding.


Data is preserved at the bit level and may be fully reconstructed
from the resulting animation.


Core Capabilities:
• Encode any file into a visual pixel stream
• Render bitmaps using a fixed symbolic color map
• Convert pixel streams into MP4 animations via FFmpeg
• Decode MP4 frames back into the original binary payload


Symbolic Color Map:
0 → Black (0,0,0)
1 → White (255,255,255)
␠ → Void → Reserved / padding region (non-significant)


Dependencies:
• Go standard library
• FFmpeg (must be available in PATH)


Design Notes:
• Deterministic encoding (no randomness)
• MSB-first bit ordering
• Fixed frame geometry (256×256)
• Optimized for archival, steganographic, and experimental use


=====================================================================
*/

package main

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"image/png"
	"io/fs"
	"os"
	"os/exec"
	"path/filepath"
	"sort"
)

const (
	FrameSize = 256
	FPS       = 30
	OUTPUT_VIDEO = "encoded.mp4"
)

var (
	black = color.RGBA{0, 0, 0, 255}
	white = color.RGBA{255, 255, 255, 255}
)

// -----------------------------
// Bits helpers
// -----------------------------
func byteToBits(b byte) [8]byte {
	var out [8]byte
	for i := 0; i < 8; i++ {
		if b&(1<<(7-i)) != 0 {
			out[i] = 1
		} else {
			out[i] = 0
		}
	}
	return out
}

func setBitPixel(img *image.RGBA, x, y int, bit byte) {
	if bit == 1 {
		img.SetRGBA(x, y, white)
	} else {
		img.SetRGBA(x, y, black)
	}
}

// -----------------------------
// Cleanup helpers
// -----------------------------
func cleanupEncodeArtifacts() {
	os.Remove("filmstrip.png")
	os.RemoveAll("frames")
}

func cleanupDecodeArtifacts() {
	os.RemoveAll("frames_dec")
}

// -----------------------------
// Encode: file -> filmstrip.png + frames/ + video
// -----------------------------
func encode(inputFile string) error {
	data, err := os.ReadFile(inputFile)
	if err != nil {
		return err
	}

	// Prepend 8-byte length header (big endian).
	buf := new(bytes.Buffer)
	if err := binary.Write(buf, binary.BigEndian, uint64(len(data))); err != nil {
		return err
	}
	if _, err := buf.Write(data); err != nil {
		return err
	}
	payload := buf.Bytes()

	totalBits := len(payload) * 8
	rows := (totalBits + FrameSize - 1) / FrameSize // exact rows needed

	film := image.NewRGBA(image.Rect(0, 0, FrameSize, rows))

	// Fill film with bits
	x, y := 0, 0
	for _, b := range payload {
		bits := byteToBits(b)
		for i := 0; i < 8; i++ {
			setBitPixel(film, x, y, bits[i])
			x++
			if x == FrameSize {
				x = 0
				y++
			}
		}
	}

	// Write filmstrip.png
	{
		f, err := os.Create("filmstrip.png")
		if err != nil {
			return err
		}
		if err := png.Encode(f, film); err != nil {
			f.Close()
			return err
		}
		f.Close()
	}

	// Slice into 256x256 frames (finite, deterministic)
	if err := os.RemoveAll("frames"); err != nil {
		return err
	}
	if err := os.MkdirAll("frames", 0755); err != nil {
		return err
	}

	numFrames := (rows + FrameSize - 1) / FrameSize
	for i := 0; i < numFrames; i++ {
		frame := image.NewRGBA(image.Rect(0, 0, FrameSize, FrameSize))

		// Copy region from film into frame; out-of-bounds area stays black.
		srcY0 := i * FrameSize
		srcRect := image.Rect(0, srcY0, FrameSize, srcY0+FrameSize)
		draw.Draw(frame, frame.Bounds(), image.NewUniform(black), image.Point{}, draw.Src)

		// Intersect with film bounds to avoid reading past end
		srcRect = srcRect.Intersect(film.Bounds())
		if !srcRect.Empty() {
			draw.Draw(frame, srcRect.Sub(image.Point{0, srcY0}), film, image.Point{0, srcY0}, draw.Src)
		}

		outName := filepath.Join("frames", fmt.Sprintf("frame_%05d.png", i))
		f, err := os.Create(outName)
		if err != nil {
			return err
		}
		if err := png.Encode(f, frame); err != nil {
			f.Close()
			return err
		}
		f.Close()
	}

	fmt.Printf("✔ Encoded %d bytes into filmstrip.png (%d rows) and %d frames\n", len(data), rows, numFrames)

	// Encode frames -> video (LOSSLESS RGB)
	// Use libx264rgb + crf 0 for lossless in an MP4 container.
	// (If your ffmpeg lacks libx264rgb, swap to ffv1 in mkv.)
	cmd := exec.Command(
		"ffmpeg",
		"-y",
		"-framerate", fmt.Sprintf("%d", FPS),
		"-i", filepath.Join("frames", "frame_%05d.png"),
		"-frames:v", fmt.Sprintf("%d", numFrames),
		"-c:v", "libx264rgb",
		"-crf", "0",
		"-preset", "veryslow",
		"-pix_fmt", "rgb24",
		OUTPUT_VIDEO,
	)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	fmt.Printf("Encoding frames into %s (finite, lossless)...\n", OUTPUT_VIDEO)
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("ffmpeg encode failed: %w", err)
	}

	fmt.Printf("✔ %s created\n", OUTPUT_VIDEO)
    cleanupEncodeArtifacts()
    fmt.Println("✔ encode artifacts cleaned")
	return nil
}

// -----------------------------
// Decode: video -> output file
// -----------------------------
func decode(videoFile, outFile string) error {
	// Extract all frames from video
	if err := os.RemoveAll("frames_dec"); err != nil {
		return err
	}
	if err := os.MkdirAll("frames_dec", 0755); err != nil {
		return err
	}

	cmd := exec.Command(
		"ffmpeg",
		"-y",
		"-i", videoFile,
		filepath.Join("frames_dec", "frame_%05d.png"),
	)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	fmt.Println("Extracting frames from video...")
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("ffmpeg decode failed: %w", err)
	}

	// Collect extracted frames, sorted
	var frames []string
	err := filepath.WalkDir("frames_dec", func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}
		if !d.IsDir() && filepath.Ext(path) == ".png" {
			frames = append(frames, path)
		}
		return nil
	})
	if err != nil {
		return err
	}
	sort.Strings(frames)
	if len(frames) == 0 {
		return fmt.Errorf("no frames extracted")
	}

	// Read bits until we have header + declared payload
	needBytes := -1
	var out []byte

	// We’ll accumulate bytes as we go to avoid storing huge rune slices.
	var curByte byte
	bitPos := 0 // 0..7

	pushBit := func(bit byte) {
		if bit == 1 {
			curByte |= (1 << (7 - bitPos))
		}
		bitPos++
		if bitPos == 8 {
			out = append(out, curByte)
			curByte = 0
			bitPos = 0
			// Once we have 8 header bytes, compute total needed output bytes.
			if needBytes < 0 && len(out) >= 8 {
				sz := binary.BigEndian.Uint64(out[:8])
				needBytes = int(8 + sz) // header + payload
			}
		}
	}

	// Conservative thresholds for pure black/white frames
	isWhite := func(r, g, b uint32) bool {
		// r,g,b are 0..65535
		// treat near-white as white
		return r > 50000 && g > 50000 && b > 50000
	}

	for _, fp := range frames {
		f, err := os.Open(fp)
		if err != nil {
			return err
		}
		img, _, err := image.Decode(f)
		f.Close()
		if err != nil {
			return err
		}

		b := img.Bounds()
		for y := 0; y < b.Dy(); y++ {
			for x := 0; x < b.Dx(); x++ {
				r, g, bb, _ := img.At(b.Min.X+x, b.Min.Y+y).RGBA()
				if isWhite(r, g, bb) {
					pushBit(1)
				} else {
					pushBit(0)
				}
				if needBytes > 0 && len(out) >= needBytes {
					// We have everything, stop early.
					payload := out[8:needBytes]
					if err := os.WriteFile(outFile, payload, 0644); err != nil {
						return err
					}
					fmt.Printf("✔ Decoded %d bytes to %s\n", len(payload), outFile)
                    cleanupDecodeArtifacts()
                    fmt.Println("✔ decode artifacts cleaned")
					return nil
				}
			}
		}
	}

	if needBytes <= 0 {
		return fmt.Errorf("failed to read header (not enough data)")
	}
	if len(out) < needBytes {
		return fmt.Errorf("truncated decode: expected %d bytes total, got %d", needBytes, len(out))
	}

	payload := out[8:needBytes]
	if err := os.WriteFile(outFile, payload, 0644); err != nil {
		return err
	}
	fmt.Printf("✔ Decoded %d bytes to %s\n", len(payload), outFile)
	return nil
}

// -----------------------------
// Main
// -----------------------------
func main() {
	if len(os.Args) < 2 {
		fmt.Println("Usage:")
		fmt.Println("  Encode: encode <inputfile>")
		fmt.Println("  Decode: decode <videofile> <outputfile>")
		os.Exit(1)
	}

	switch os.Args[1] {
	case "encode":
		if len(os.Args) < 3 {
			fmt.Println("encode requires <inputfile>")
			os.Exit(1)
		}
		if err := encode(os.Args[2]); err != nil {
			fmt.Println("ERROR:", err)
			os.Exit(1)
		}

	case "decode":
		if len(os.Args) < 4 {
			fmt.Println("decode requires <videofile> <outputfile>")
			os.Exit(1)
		}
		if err := decode(os.Args[2], os.Args[3]); err != nil {
			fmt.Println("ERROR:", err)
			os.Exit(1)
		}

	default:
		fmt.Println("Unknown mode:", os.Args[1])
		os.Exit(1)
	}
}

