#******************************************************************************
#
# Copyright:   2018-2025 Paul Obermeier (obermeier@poSoft.de)
#
#              See the file "Mawt_License.txt" for information on
#              usage and redistribution of this file, and for a
#              DISCLAIMER OF ALL WARRANTIES.
#
# Description: Tcl module with TclOO classes for Video and Vector handling.
#
#******************************************************************************

namespace eval mawt {
    variable ns [namespace current]

    namespace ensemble create

    namespace export Vector
    namespace export Video

    oo::class create Video {
        variable mVideoFile
        variable mVideoId
        variable mMode
        variable mNumBytes

        constructor { fileName mode args } {
            set mVideoFile ""
            set mVideoId   "NULL"
            set mNumBytes  -1

            set width     256
            set height    256
            set fps        25
            set bitrate     4.0 

            foreach { key value } $args {
                switch -exact $key {
                    "-width" {
                        set width [expr {int($value)}]
                    }
                    "-height" {
                        set height [expr {int($value)}]
                    }
                    "-fps" {
                        set fps [expr {int($value)}]
                    }
                    "-bitrate" {
                        set bitrate [expr {double($value)}]
                    }
                }
            }

            if { $mode eq "r" } {
                my Open $fileName
            } else {
                my Create $fileName $width $height $fps $bitrate
            }
            set mMode $mode
        }

        destructor {
            if { [my IsOpen] } {
                my Close
            }
        }

        method IsOpen {} {
            if { $mVideoId ne "NULL" } {
                return true
            } else {
                return false
            }
        }
        export IsOpen

        method Open { fileName } {
            # Open a video file.
            #
            # fileName - Open the video stream stored in specified file.
            #
            # If a video stream is already open, it is closed before opening
            # the new video stream.
            #
            # Throws an error, if the file could not be openend.
            #
            # See also: Close Start Stop

            if { [my IsOpen] } {
                my Close
            }
            set mVideoId [mawt::MawtVideoOpen $fileName]
            if { ! [my IsOpen] } {
                throw [list Video Open] [mawt::MawtGetErrorMessage]
            }
            set mVideoFile $fileName
        }
        export Open

        method Create { fileName width height fps bitrate } {
            # Create a new empty video file.
            #
            # fileName - Create the video stream in specified file.
            # width    - Width of the video stream.
            # height   - Height of the video stream.
            # fps      - Framerate of the video streams (frames per second).
            # birate   - Bit rate of the video ostream in MBits/s.
            #
            # Throws an error, if the file could not be openend.
            #
            # See also: Close WriteNextImage WriteNextPhotoImage

            if { [my IsOpen] } {
                my Close
            }
            set mVideoId [mawt::MawtVideoCreate $fileName $width $height $fps $bitrate]
            if { ! [my IsOpen] } {
                throw [list Video Create] [mawt::MawtGetErrorMessage]
            }
            set mVideoFile $fileName
        }
        export Create

        method Close {} {
            # Close a video stream.
            #
            # See also: Open

            if { $mMode eq "r" } {
                mawt::MawtVideoClose $mVideoId
            } else {
                if {! [mawt::MawtVideoFinish $mVideoId] } {
                    throw [list Video Close] [mawt::MawtGetErrorMessage]
                }
            }
            set mVideoId   "NULL"
            set mVideoFile ""
        }
        export Close

        method Start { { width -1 } { height -1 } } {
            # Start acquisition of a video stream.
            #
            # width   - integer
            # height  - integer
            #
            # Start acquisition of current video stream.
            # If $width and $height are specified and greater than
            # zero, stretch the video to that size.
            # Otherwise the video is played in its original size.
            #
            # Returns the number of bytes needed for a video frame.
            # If the video could not be started, an error is thrown.
            #
            # See also: Open Stop

            set mNumBytes [mawt::MawtVideoStart $mVideoId $width $height]
            if { $mNumBytes < 0 } {
                throw [list Video Start] [mawt::MawtGetErrorMessage]
            }
            return $mNumBytes
        }
        export Start

        method Stop {} {
            # Stop acquisition of a video stream.
            #
            # See also: Start Open Close

            mawt::MawtVideoStop $mVideoId
        }
        export Stop

        method Lock {} {
            # Lock a video stream.
            #
            # See also: Open Unlock

            mawt::MawtVideoLock $mVideoId
        }
        export Lock

        method Unlock {} {
            # Unlock a video stream.
            #
            # See also: Open Lock

            mawt::MawtVideoUnlock $mVideoId
        }
        export Unlock

        method GetImage { img flip num } {
            # Get an image from a video stream.
            #
            # img  - A Vector of appropriate size to store the image data.
            # flip - Boolean value to specify image flipping.
            # num  - Image number as integer value starting at zero.
            #
            # Get image $num from current video stream.
            # The image will be stored in variable $img.
            # $img must point to a buffer of appropriate size.
            # If $flip is set to true, the image is returned in
            # bottom-up scanline order as needed by OpenGL.
            # If $flip is set to false, the image is returned in
            # top-down scanline order as needed by the img::raw extension.
            #
            # Throws an error, if the image could not be retrieved
            # from the video stream.
            #
            # See also: Open GetNextImage GetWidth GetHeight

            if { ! [mawt::MawtVideoGetImage $mVideoId $img [expr {bool($flip)}] $num] } {
                throw [list Video GetImage] [mawt::MawtGetErrorMessage]
            }
        }
        export GetImage

        method GetNextImage { img flip } {
            # Get next image from a video stream.
            #
            # img  - A Vector of appropriate size to store the image data.
            # flip - Boolean value to specify image flipping.
            #
            # Get the next image from current video stream.
            # The image will be stored in variable $img.
            # $img must point to a buffer of appropriate size.
            # If $flip is set to true, the image is returned in
            # bottom-up scanline order as needed by OpenGL.
            # If $flip is set to false, the image is returned in
            # top-down scanline order as needed by the img::raw extension.
            #
            # Throws an error, if the image could not be retrieved
            # from the video stream.
            #
            # See also: Open Close GetImage GetWidth GetHeight

            if { ! [mawt::MawtVideoGetNextImage $mVideoId $img [expr {bool($flip)}]] } {
                # TODO: Check why this throws an error at the last frame.
                # throw [list Video GetNextImage] [mawt::MawtGetErrorMessage]
            }
        }
        export GetNextImage

        method WriteNextImage { img width height flip } {
            # Write next image to a video stream.
            #
            # img    - A Vector containing the image data.
            # width  - Width of the image.
            # height - Height of the image.
            # flip   - Boolean value to specify image flipping.
            #
            # Write the next image to the current video stream.
            #
            # The image data must be available in variable $img.
            # $img must point to a buffer of size width by height.
            # If $flip is set to true, the image is returned in
            # bottom-up scanline order as needed by OpenGL.
            # If $flip is set to false, the image is returned in
            # top-down scanline order as needed by the img::raw extension.
            #
            # Throws an error, if the image could not be written
            # to the video stream.
            #
            # See also: WriteNextPhotoImage Create Close GetImage GetWidth GetHeight

            if { ! [mawt::MawtVideoWriteNextImage $mVideoId $img $width $height [expr {bool($flip)}]] } {
                # TODO: Check why this throws an error at the last frame.
                # throw [list Video WriteNextImage] [mawt::MawtGetErrorMessage]
            }
        }
        export WriteNextImage

        method WriteNextPhotoImage { phImg flip } {
            # Write next photo image to a video stream.
            #
            # phImg - A Tk photo image.
            # flip  - Boolean value to specify image flipping.
            #
            # The image data must be available in photo image $phImg.
            #
            # If $flip is set to true, the image is returned in
            # bottom-up scanline order as needed by OpenGL.
            # If $flip is set to false, the image is returned in
            # top-down scanline order as needed by the img::raw extension.
            #
            # Throws an error, if the image could not be written
            # to the video stream.
            #
            # See also: WriteNextImage Create Close GetImage GetWidth GetHeight

            set w [image width  $phImg]
            set h [image height $phImg]
            set vectorObj [mawt Vector new [expr {$w * $h * 3}]]
            $vectorObj FromPhoto $phImg 3 1 0
            if { ! [mawt::MawtVideoWriteNextImage $mVideoId [$vectorObj Get] $w $h [expr {bool($flip)}]] } {
                # TODO: Check why this throws an error at the last frame.
                # throw [list Video WriteNextImage] [mawt::MawtGetErrorMessage]
            }
            $vectorObj destroy
        }
        export WriteNextPhotoImage

        method GetWidth {} {
            # Get the width of a video stream.
            #
            # Returns the width of a video stream in pixels.
            #
            # See also: Open GetHeight

            return [mawt::MawtVideoGetWidth $mVideoId]
        }
        export GetWidth

        method GetHeight {} {
            # Get the height of a video stream.
            #
            # Returns the height of a video stream in pixels.
            #
            # See also: Open GetWidth

            return [mawt::MawtVideoGetHeight $mVideoId]
        }
        export GetHeight

        method GetFramerate {} {
            return [mawt::MawtVideoGetFramerate $mVideoId]
        }
        export GetFramerate

        method GetNumFrames {} {
            return [mawt::MawtVideoGetNumFrames $mVideoId]
        }
        export GetNumFrames

        method GetDuration {} {
            # Get video duration information as a dict.
            #
            # Get the duration of current video stream.
            # The duration is returned as a dictionary with the
            # keys: `minutes`, `seconds`, `frames`.
            #
            # See also: GetDurationAsString

            set numFrames [mawt::MawtVideoGetNumFrames $mVideoId]
            set frameRate [expr int([mawt::MawtVideoGetFramerate $mVideoId] + 0.5)]

            set secs [expr $numFrames / $frameRate]
            dict set durDict minutes [expr {$secs / 60}]
            dict set durDict seconds [expr {$secs - [dict get $durDict minutes] * 60}]
            dict set durDict frames  [expr {$numFrames % $frameRate}] 
            return $durDict
        }
        export GetDuration

        method GetDurationAsString {} {
            # Get video duration information as a string.
            #
            # Get the duration of current video stream.
            # The duration is returned as a string in the following
            # format: `minutes:seconds.frames`.
            #
            # See also: GetDuration

            set durDict [my GetDuration]
            return [format "%02d:%02d.%02d" \
                       [dict get $durDict minutes] \
                       [dict get $durDict seconds] \
                       [dict get $durDict frames] \
                   ]
        }
        export GetDurationAsString
    }

    oo::class create Vector {
        variable mVec

        constructor { numBytes } {
            if { $numBytes > 0 } {
                set mVec [mawt::new_UByte $numBytes]
            }
        }

        destructor {
            mawt::delete_UByte $mVec
        }

        method Get {} {
            return $mVec
        }
        export Get

        method FromByteArray { str } {
            # Create a Vector from a binary string.
            #
            # str - Binary string.
            #
            # Create a new Vector from given binary string $str.
            #
            # See also: ToByteArray

            set numBytes [string length $str]
            if { $numBytes == 0 } {
                error "String has zero length"
            }
            set mVec [mawt::new_UByte $numBytes]
            mawt::VectorFromByteArray $str $mVec $numBytes 0 0
        }
        export FromByteArray

        method ToByteArray { numBytes { srcOff 0 } { destOff 0 } } {
            # Copy a Vector into a binary string.
            #
            # numBytes - int
            # srcOff   - int
            # destOff  - int
            #
            # Copy $numBytes elements into a Tcl binary string and return that string.
            # $srcOff and $destOff may be used optionally to specify
            # an offset into the source and the destination.
            #
            # See also: FromByteArray

            if { $numBytes == 0 } {
                error "Vector has zero length"
            }
            # First generate a binary string of appropriate size.
            set bytes [binary format a$numBytes 0]
            mawt::VectorToByteArray $mVec $bytes $numBytes $srcOff $destOff
            return $bytes
        }
        export ToByteArray

        method FromPhoto { phImg numChans scale offset } {
            return [mawt::VectorFromPhoto $phImg $mVec $numChans $scale $offset]
        }
        export FromPhoto

        method ToPhoto { phImg width height { numChans 3 } { linesAreBottomUp 0 } } {
            mawt::VectorToPhoto $mVec $phImg $width $height $numChans $linesAreBottomUp
        }
        export ToPhoto
    }
}
