Imports System.Text
Imports System.Runtime.Serialization
Imports System.Reflection
Imports System.Text.RegularExpressions

Imports Stax
Imports Stax.UI
Imports Stax.AppFramework

<Serializable()> _
Public MustInherit Class Muxer
    Inherits Profile

    Public MustOverride Sub Mux()

    Public Sub New(ByVal name As String, ByVal typeName As String)
        MyBase.New(name, typeName)
        CanEditValue = True
    End Sub

    Private ChapterFileValue As String = ""

    Public Property ChapterFile() As String
        Get
            Return ChapterFileValue
        End Get
        Set(ByVal Value As String)
            ChapterFileValue = Value
        End Set
    End Property

    Private SupportsIDXValue As Boolean

    Public Property SupportsIDX() As Boolean
        Get
            Return SupportsIDXValue
        End Get
        Set(ByVal Value As Boolean)
            SupportsIDXValue = Value
        End Set
    End Property

    Protected SupportsCuttingValue As Boolean

    Public ReadOnly Property SupportsCutting() As Boolean
        Get
            Return SupportsCuttingValue
        End Get
    End Property

    Protected InputTypesValue As StreamType()

    Public Overridable ReadOnly Property InputTypes() As StreamType()
        Get
            Return InputTypesValue
        End Get
    End Property

    Protected OutputTypeValue As StreamType

    Public Overridable ReadOnly Property OutputType() As StreamType
        Get
            Return OutputTypeValue
        End Get
    End Property

    Private IDXValue As IDX

    Public Property IDX() As IDX
        Get
            If IDXValue Is Nothing Then
                IDXValue = New IDX
            End If

            Return IDXValue
        End Get
        Set(ByVal value As IDX)
            IDXValue = value
        End Set
    End Property

    Public Overrides Sub Clean()
        If SupportsIDX Then
            IDX.Path = ""
            IDX.Subtitles.Clear()
        End If

        ChapterFile = ""
    End Sub

    Public Overridable Function GetExtension() As String
        Return StreamTypeUtils.GetExtension(OutputType)
    End Function

    Public Overridable Sub Init()
        If Not File.Exists(p.SourceFile) Then
            Exit Sub
        End If

        Dim files As String() = Directory.GetFiles(Filepath.GetDir(p.SourceFile))

        If SupportsIDX Then
            For Each i As String In files
                If Filepath.GetExt(i) = ".idx" AndAlso g.BelongsToSource(i) Then
                    IDX.Path = i
                    Exit For
                End If
            Next
        End If

        For Each i As String In files
            If i.EndsWith("Chapter Information - OGG.txt") AndAlso g.BelongsToSource(i) Then
                ChapterFile = i
                Exit For
            End If
        Next
    End Sub

    Public Overridable Function IsStreamSupported(ByVal type As StreamType) As Boolean
        Return Array.IndexOf(Of StreamType)(InputTypes, type) > -1
    End Function

    Public Shared Function GetDefaults(ByVal advanced As Boolean) As List(Of Muxer)
        Dim ret As New List(Of Muxer)

        ret.Add(New EncoderMuxer("AVI", StaxRip.StreamType.AVI, New StreamType() {StreamType.AC3, StreamType.MP3CBR, StreamType.MP3VBR, StreamType.AVI}, GetType(VirtualDubEncoder), True))
        ret.Add(New mkvmergeMuxer)
        ret.Add(New MP4BoxMuxer)
        ret.Add(New DMFMuxer)
        ret.Add(New PMPMuxer)

        If advanced Then
            Dim mp4CLI As New CmdlMuxer(StreamType.MP4)
            mp4CLI.Name = "Command Line Example"
            mp4CLI.CommandLines.Value = """%application:MP4Box%"" -nodrop -add ""%encoder_out_file%#video"" -add ""%audio_file1%"" -new ""%target_file%"""
            ret.Add(mp4CLI)

            ret.Add(New NullMuxer)
        End If

        Return ret
    End Function

    Public Overrides Function GetInfoValues() As Dictionary(Of String, String)
        Dim ret As New Dictionary(Of String, String)

        ret("Muxer Type") = TypeName
        ret("Output File Extension") = GetExtension()
        ret("Supported Input Formats") = StreamTypeUtils.GetNames(InputTypes)
        ret("Output Format") = StreamTypeUtils.GetName(OutputType)

        Return ret
    End Function

    Public Overrides Function Edit() As DialogResult
        ShowInfoDialog()
    End Function
End Class

<Serializable()> _
Public Class EncoderMuxer
    Inherits Muxer

    Public Sub New(ByVal name As String, ByVal outputType As StreamType, ByVal inputTypes As StreamType(), ByVal encoder As Type, ByVal canCut As Boolean)
        MyBase.New(name, "Encoder Muxer")
        OutputTypeValue = outputType
        InputTypesValue = inputTypes
        SupportsCuttingValue = canCut
        EncoderValue = encoder
        CanEditValue = False
    End Sub

    Private EncoderValue As Type

    Public ReadOnly Property Encoder() As Type
        Get
            Return EncoderValue
        End Get
    End Property

    Public Overrides Function GetExtension() As String
        Return p.VideoEncoder.GetExtension
    End Function

    Public Overrides Sub Mux()
    End Sub
End Class

<Serializable()> _
Public Class MP4BoxMuxer
    Inherits Muxer

    Public Sub New()
        MyBase.New("MP4", "MP4Box Muxer")
        OutputTypeValue = StaxRip.StreamType.MP4
    End Sub

    <OptionalField()> _
    Private SRTSubtitlesValue As List(Of SRTSubtitle)

    Public ReadOnly Property SRTSubtitles() As List(Of SRTSubtitle)
        Get
            If SRTSubtitlesValue Is Nothing Then
                SRTSubtitlesValue = New List(Of SRTSubtitle)
            End If

            Return SRTSubtitlesValue
        End Get
    End Property

    Public Overrides Function Edit() As DialogResult
        Using f As New MP4BoxForm(Me)
            Return f.ShowDialog()
        End Using
    End Function

    Public Function GetCommandLine() As String
        Return """" + Paths.MP4Box.GetPath(False) + """ " + GetArgs()
    End Function

    Private Function GetArgs() As String
        Dim args As New StringBuilder

        If p.VideoEncoder.OutputStreamType = StreamType.RAW264 Then
            args.Append(" -fps " + p.TargetAVSDoc.GetFramerate.ToString("f3").Replace(",", "."))
        End If

        If IsStreamSupported(p.VideoEncoder.OutputStreamType) Then
            args.Append(" -add """ + p.VideoEncoder.OutputPath + ":name=Video""")
        End If

        If File.Exists(p.Audio0.File) AndAlso IsStreamSupported(p.Audio0.Profile.StreamType) Then
            args.Append(" -add """ + p.Audio0.File + ":lang=" + p.Audio0.Language.ThreeLetterCode)

            If p.Audio0.Delay <> 0 AndAlso Not p.Audio0.Profile.HandlesDelay Then
                args.Append(":delay=" + p.Audio0.Delay.ToString)
            End If

            If p.Audio0.Profile.StreamName <> "" Then
                args.Append(":name=" + p.Audio0.Profile.SolveMacros(p.Audio0.Profile.StreamName))
            End If

            If p.Audio0.Profile.StreamType = StreamType.AAC_HE_MP4 OrElse p.Audio0.Profile.StreamType = StreamType.AAC_HE_RAW Then
                args.Append(":sbr")
            End If

            args.Append("""")
        End If

        If File.Exists(p.Audio1.File) AndAlso IsStreamSupported(p.Audio1.Profile.StreamType) AndAlso p.Audio1.File <> "" Then
            args.Append(" -add """ + p.Audio1.File + ":lang=" + p.Audio1.Language.ThreeLetterCode)

            If p.Audio1.Delay <> 0 AndAlso Not p.Audio1.Profile.HandlesDelay Then
                args.Append(":delay=" + p.Audio1.Delay.ToString)
            End If

            If p.Audio1.Profile.StreamName <> "" Then
                args.Append(":name=" + p.Audio1.Profile.SolveMacros(p.Audio1.Profile.StreamName))
            End If

            If p.Audio1.Profile.StreamType = StreamType.AAC_HE_MP4 OrElse p.Audio0.Profile.StreamType = StreamType.AAC_HE_RAW Then
                args.Append(":sbr")
            End If

            args.Append("""")
        End If

        For Each i As SRTSubtitle In SRTSubtitles
            If File.Exists(i.Path.Value) Then
                Dim name As String = ""

                If i.Name.Value <> "" Then
                    name = ":name=" + i.Name.Value
                End If

                args.Append(" -add """ + i.Path.Value + ":lang=" + i.Language.Value.ThreeLetterCode + name + """")
            End If
        Next

        If File.Exists(ChapterFile) Then
            args.Append(" -chap """ + ChapterFile + """")
        End If

        args.Append(" -new """ + p.TargetFile + """")

        Return args.ToString
    End Function

    Public Overrides Sub Mux()
        Log.WriteHeader("Muxing")

        Dim start As DateTime = DateTime.Now

        Dim pw As New ProcessWrapper

        pw.File = Paths.MP4Box.GetPath(False)
        pw.Arguments = GetArgs()

        Log.WriteLine("Command Line: " + pw.CommandLine)

        pw.Minimized = s.RunAppsMinimized.Value
        pw.Wait = True
        pw.Priority = s.ProcessPriority.Value

        ProcessForm.ProcessWrapper = pw

        pw.Start()

        Log.WriteHeader("Muxing Statistics")
        Log.WriteStats(start, p.TargetAVSDoc.GetFrames)
    End Sub

    Public Overrides Sub Clean()
        SRTSubtitles.Clear()
    End Sub

    Public Overrides ReadOnly Property InputTypes() As StreamType()
        Get
            Return New StreamType() {StreamType.AVI, StreamType.RAW264, StreamType.MP4, StreamType.AAC_HE_MP4, StreamType.AAC_LC_MP4, StreamType.AAC_HE_RAW, StreamType.AAC_LC_RAW}
        End Get
    End Property
End Class

<Serializable()> _
Public Class NullMuxer
    Inherits Muxer

    Public Sub New()
        MyBase.New("No Muxing", "Null Muxer")
        CanEditValue = False
    End Sub

    Public Overrides Function IsStreamSupported(ByVal type As StreamType) As Boolean
        Return True
    End Function

    Public Overrides ReadOnly Property OutputType() As StreamType
        Get
            Return p.VideoEncoder.OutputStreamType
        End Get
    End Property

    Public Overrides Function GetExtension() As String
        Return p.VideoEncoder.GetExtension
    End Function

    Public Overrides Sub Mux()
    End Sub
End Class

<Serializable()> _
Public Class CmdlMuxer
    Inherits Muxer

    Public Sub New(ByVal outputType As StreamType)
        MyBase.New("Command Line", "Command Line Muxer")
        OutputTypeValue = outputType
    End Sub

    Private CommandLinesValue As New SettingBag(Of String)("")

    Public Property CommandLines() As SettingBag(Of String)
        Get
            Return CommandLinesValue
        End Get
        Set(ByVal Value As SettingBag(Of String))
            CommandLinesValue = Value
        End Set
    End Property

    Public Overrides Function IsStreamSupported(ByVal type As StreamType) As Boolean
        Return True
    End Function

    Public Overrides Sub Mux()
        Log.WriteHeader("Muxing")

        For Each i As String In StringHelp.SplitLines(Macro.Solve(CommandLines.Value))
            Dim start As DateTime = DateTime.Now

            Dim pw As New ProcessWrapper

            pw.CommandLine = i
            pw.Wait = True
            pw.Minimized = s.RunAppsMinimized.Value
            pw.Priority = s.ProcessPriority.Value

            Log.WriteLine("Command Line: " + i)
            ProcessForm.ProcessWrapper = pw

            pw.Start()

            Log.WriteHeader("Muxing Statistics")
            Log.WriteStats(start, p.TargetAVSDoc.GetFrames)
        Next
    End Sub

    Public Overrides Function Edit() As DialogResult
        Using f As New SettingTreeViewForm(Name, "The Command Line Muxer dialog allows to configure StaxRip to use any command line compatible muxer.")
            Dim outputTypeSetting As New SettingBag(Of StreamType)(OutputType)

            f.SettingTreeView.AddSetting(New ComboboxTreeNodeSetting(Of StreamType)("Output Type", "General", outputTypeSetting, "Stream type of the output file."))
            f.SettingTreeView.AddSetting(New CmdlEditorTreeNodeSetting("Command Lines", "General", CommandLines, "Command lines to be executed performing the muxing. It's possible to use multible command lines separated by a new line. The command lines may contain macros."))

            f.SettingTreeView.Nodes(0).ExpandAll()
            f.Document.WriteTable("Macros", Macro.GetTips)

            Dim ret As DialogResult = f.ShowDialog()

            If ret = DialogResult.OK Then
                OutputTypeValue = outputTypeSetting.Value
            End If

            Return ret
        End Using
    End Function
End Class

<Serializable()> _
Public Class mkvmergeMuxer
    Inherits Muxer

    Private Subtitles As List(Of Subtitle)

    Public Sub New()
        MyBase.New("MKV", "mkvmerge Muxer")
        OutputTypeValue = StaxRip.StreamType.MKV
        SupportsIDX = True
    End Sub

    Public Overrides Function Edit() As DialogResult
        Using f As New mkvmergeMuxerForm(Me)
            Return f.ShowDialog()
        End Using
    End Function

    Public Overrides Sub Mux()
        Log.WriteHeader("Muxing")

        Dim start As DateTime = DateTime.Now

        Dim pw As New ProcessWrapper
        ProcessForm.ProcessWrapper = pw

        pw.File = Paths.Mkvmerge.GetPath(False)
        pw.Arguments = GetArgs()

        Log.WriteLine("Command Line: " + pw.CommandLine)

        pw.Minimized = s.RunAppsMinimized.Value
        pw.Wait = True
        pw.Priority = s.ProcessPriority.Value
        pw.Start()

        Log.WriteHeader("Muxing Statistics")
        Log.WriteStats(start, p.TargetAVSDoc.GetFrames)
    End Sub

    Public Function GetCommandLine() As String
        Return """" + Paths.Mkvmerge.GetPath(False) + """ " + GetArgs()
    End Function

    Private Function GetArgs() As String
        Dim args As New StringBuilder("-o """ + Filepath.GetDirAndNameNoExt(p.TargetFile) + ".mkv""")

        args.Append(" --track-name 0:Video")
        args.Append(" """ + p.VideoEncoder.OutputPath + """")

        If File.Exists(p.Audio0.File) AndAlso IsStreamSupported(p.Audio0.Profile.StreamType) Then
            If p.Audio0.Profile.StreamType = StreamType.AAC_HE_MP4 OrElse p.Audio0.Profile.StreamType = StreamType.AAC_HE_RAW Then
                args.Append(" --aac-is-sbr 1")
            End If

            Dim id As String = "0"

            If Filepath.GetExt(p.Audio0.File) = ".mp4" Then
                id = "1"
            End If

            args.Append(" --language " + id + ":")
            args.Append(p.Audio0.Language.ThreeLetterCode)

            If p.Audio0.Profile.StreamName <> "" Then
                args.Append(" --track-name """ + id + ":" + p.Audio0.Profile.SolveMacros(p.Audio0.Profile.StreamName) + """")
            End If

            If p.Audio0.Delay <> 0 AndAlso Not p.Audio0.Profile.HandlesDelay Then
                args.Append(" --sync " + id + ":" + p.Audio0.Delay.ToString)
            End If

            args.Append(" """ + p.Audio0.File + """")
        End If

        If File.Exists(p.Audio1.File) AndAlso IsStreamSupported(p.Audio1.Profile.StreamType) Then
            If p.Audio1.Profile.StreamType = StreamType.AAC_HE_MP4 OrElse p.Audio0.Profile.StreamType = StreamType.AAC_HE_RAW Then
                args.Append(" --aac-is-sbr 1")
            End If

            Dim id As String = "0"

            If Filepath.GetExt(p.Audio1.File) = ".mp4" Then
                id = "1"
            End If

            args.Append(" --language " + id + ":")
            args.Append(p.Audio1.Language.ThreeLetterCode)

            If p.Audio1.Profile.StreamName <> "" Then
                args.Append(" --track-name """ + id + ":" + p.Audio1.Profile.SolveMacros(p.Audio1.Profile.StreamName) + """")
            End If

            If p.Audio1.Delay <> 0 AndAlso Not p.Audio1.Profile.HandlesDelay Then
                args.Append(" --sync " + id + ":" + p.Audio1.Delay.ToString)
            End If

            args.Append(" """ + p.Audio1.File + """")
        End If

        If p.VideoEncoder.Muxer.SupportsIDX Then
            Dim subs As New List(Of String)

            For Each i As Subtitle In p.VideoEncoder.Muxer.IDX.Subtitles
                If i.Enabled Then
                    subs.Add(i.Index.ToString())

                    args.Append(" --language " + i.Index.ToString() + _
                        ":" + i.Language.ThreeLetterCode)
                End If
            Next

            If subs.Count > 0 Then
                Dim tracks As String = String.Join(",", subs.ToArray())
                args.Append(" -s " + tracks + " """ + p.VideoEncoder.Muxer.IDX.Path + """")
            End If
        End If

        args.Append(" --title """ + Filepath.GetNameNoExt(p.SourceFile) + """")

        If File.Exists(ChapterFile) Then
            args.Append(" --chapters """ + ChapterFile + """")
        End If

        Return args.ToString
    End Function

    Public Overrides ReadOnly Property InputTypes() As StreamType()
        Get
            Return New StreamType() {StreamType.AVI, StreamType.MKV, StreamType.MP4, StreamType.AAC_HE_MP4, StreamType.AAC_LC_MP4, StreamType.AAC_HE_RAW, StreamType.AAC_LC_RAW, StreamType.AC3, StreamType.MP3CBR, StreamType.MP3VBR, StreamType.Vorbis, StreamType.DTS}
        End Get
    End Property
End Class

<Serializable()> _
Public Class DMFMuxer
    Inherits Muxer

    Private Subtitles As List(Of Subtitle)

    Public Sub New()
        MyBase.New("DIVX", "DivXMux")
        OutputTypeValue = StreamType.DMF
        InputTypesValue = New StreamType() {StreamType.AVI, StreamType.DMF, StreamType.AC3, StreamType.MP3CBR, StreamType.MP3VBR}
        SupportsIDX = True
    End Sub

    Public Overrides Function Edit() As DialogResult
        Using f As New DMFMuxerForm(Me)
            Return f.ShowDialog()
        End Using
    End Function

    Public Function GetCommandLine() As String
        Return """" + Paths.DivXMux.GetPath(False) + """ " + GetArgs()
    End Function

    Private Function GetArgs() As String
        Dim args As New StringBuilder("-v """ + p.VideoEncoder.OutputPath + """")

        If File.Exists(p.Audio0.File) AndAlso IsStreamSupported(p.Audio0.Profile.StreamType) Then
            args.Append(" -a (Audio -" + p.Audio0.Language.TwoLetterCode + ";01)")

            If p.Audio0.Delay <> 0 AndAlso Not p.Audio0.Profile.HandlesDelay Then
                args.Append(" o" + p.Audio0.Delay.ToString)
            End If

            p.Audio0.File = Filepath.GetChangeExt(p.Audio0.File, Filepath.GetExt(p.Audio0.File))
            args.Append(" """ + p.Audio0.File + """")
        End If

        If File.Exists(p.Audio1.File) AndAlso IsStreamSupported(p.Audio1.Profile.StreamType) Then
            args.Append(" -a (Audio -" + p.Audio1.Language.TwoLetterCode + ";01)")

            If p.Audio1.Delay <> 0 AndAlso Not p.Audio1.Profile.HandlesDelay Then
                args.Append(" o" + p.Audio1.Delay.ToString)
            End If

            p.Audio1.File = Filepath.GetChangeExt(p.Audio1.File, Filepath.GetExt(p.Audio1.File))
            args.Append(" """ + p.Audio1.File + """")
        End If

        For Each i As Subtitle In IDX.Subtitles
            If i.Enabled Then
                Dim content As String = StringHelp.ReadFile(IDX.Path)
                content = Regex.Replace(content, "langidx: \d+", "langidx: " + i.Index.ToString)
                Dim newIDXPath As String = Paths.GetTempDir + i.Language.TwoLetterCode + i.Index.ToString + ".idx"
                StringHelp.WriteFile(newIDXPath, content)
                File.Copy(Filepath.GetDirAndNameNoExt(IDX.Path) + ".sub", Paths.GetTempDir + i.Language.TwoLetterCode + i.Index.ToString + ".sub", True)
                args.Append(" -s (Subtitle -" + i.Language.TwoLetterCode + ";01) """ + newIDXPath + """")
            End If
        Next

        args.Append(" -o """ + p.TargetFile + """")

        Return args.ToString
    End Function

    Public Overrides Sub Mux()
        Log.WriteHeader("Muxing")

        Dim start As DateTime = DateTime.Now

        Dim pw As New ProcessWrapper

        pw.File = Paths.DivXMux.GetPath(False)
        pw.Arguments = GetArgs()

        Log.WriteLine("Command Line: " + pw.CommandLine)

        pw.Minimized = s.RunAppsMinimized.Value
        pw.Wait = True
        pw.Priority = s.ProcessPriority.Value

        ProcessForm.ProcessWrapper = pw

        pw.Start()

        Log.WriteHeader("Muxing Statistics")
        Log.WriteStats(start, p.TargetAVSDoc.GetFrames)
    End Sub
End Class

<Serializable()> _
Public Class PMPMuxer
    Inherits Muxer

    Public Sub New()
        MyBase.New("PMP (PlayStation Portable)", "PMP Muxer")
        OutputTypeValue = StreamType.PMP
        InputTypesValue = New StreamType() {StreamType.AVI, StreamType.MP3CBR}
    End Sub

    Public Overrides Sub Mux()
        Log.WriteHeader("Muxing")

        Dim start As DateTime = DateTime.Now

        Dim pw As New ProcessWrapper

        pw.File = Paths.PMPMuxer.GetPath(False)
        pw.Arguments = """" + p.VideoEncoder.OutputPath + """ """ + p.Audio0.File + """ """ + p.TargetFile + """"
        pw.Wait = True
        pw.Minimized = s.RunAppsMinimized.Value
        pw.Priority = s.ProcessPriority.Value

        Log.WriteLine("Command Line: " + pw.CommandLine)
        ProcessForm.ProcessWrapper = pw

        pw.Start()

        Log.WriteHeader("Muxing Statistics")
        Log.WriteStats(start, p.TargetAVSDoc.GetFrames)
    End Sub
End Class