/*  HBVideoController.m $

 This file is part of the HandBrake source code.
 Homepage: <http://handbrake.fr/>.
 It may be used under the terms of the GNU General Public License. */

#import "HBVideoController.h"

#include "hb.h"

#import "Controller.h"
#import "HBAdvancedController.h"

NSString *HBVideoEncoderChangedNotification = @"HBVideoEncoderChangedNotification";

@interface HBVideoController () {
    /* Framerate */
    /* Radio Button Framerate Controls */
    IBOutlet NSMatrix            * fFramerateMatrix;
    IBOutlet NSButtonCell        * fFramerateVfrPfrCell;
    IBOutlet NSButtonCell        * fFramerateCfrCell;

    /* Video Encoder */
    IBOutlet NSTextField         * fVidRateField;
    IBOutlet NSPopUpButton       * fVidRatePopUp;
    IBOutlet NSTextField         * fVidEncoderField;
    IBOutlet NSPopUpButton       * fVidEncoderPopUp;
    IBOutlet NSTextField         * fVidQualityField;
    IBOutlet NSTextField         * fVidQualityRFLabel;
    IBOutlet NSTextField         * fVidQualityRFField;
    IBOutlet NSMatrix            * fVidQualityMatrix;
    IBOutlet NSButtonCell        * fVidBitrateCell;
    IBOutlet NSTextField         * fVidBitrateField;
    IBOutlet NSButtonCell        * fVidConstantCell;
    IBOutlet NSSlider            * fVidQualitySlider;
    IBOutlet NSButton            * fVidTwoPassCheck;
    IBOutlet NSButton            * fVidTurboPassCheck;

    /* Status read out fields for picture settings and video filters */
    IBOutlet NSTextField         * fPictureSettingsField;
    IBOutlet NSTextField         * fPictureFiltersField;


    /* Encoder options views */
    IBOutlet NSView         *fPresetView;
    IBOutlet NSView         *fSimplePresetView;

    IBOutlet NSTextField    *fEncoderOptionsLabel;

    /* Simple Presets Box */
    IBOutlet NSTextField    *fLavcOptionsTextField;
    IBOutlet NSTextField    *fLavcOptionsLabel;

    /* x264/x265 Presets Box */
    NSArray                      * fPresetNames;
    NSUInteger                     fMediumPresetIndex;
    IBOutlet NSButton            * fX264UseAdvancedOptionsCheck;
    IBOutlet NSBox               * fDividerLine;
    IBOutlet NSBox               * fPresetsBox;
    IBOutlet NSSlider            * fPresetsSlider;
    IBOutlet NSTextField         * fPresetSliderLabel;
    IBOutlet NSTextField         * fPresetSelectedTextField;
    IBOutlet NSPopUpButton       * fTunePopUp;
    IBOutlet NSTextField         * fTunePopUpLabel;
    IBOutlet NSPopUpButton       * fProfilePopUp;
    IBOutlet NSTextField         * fProfilePopUpLabel;
    IBOutlet NSPopUpButton       * fLevelPopUp;
    IBOutlet NSTextField         * fLevelPopUpLabel;
    IBOutlet NSButton            * fFastDecodeCheck;
    IBOutlet NSTextField         * fDisplayPresetsAdditonalOptionsTextField;
    IBOutlet NSTextField         * fDisplayPresetsAdditonalOptionsLabel;
    // Text Field to show the expanded opts from unparse()
    IBOutlet NSTextField         * fDisplayX264PresetsUnparseTextField;
    char                         * fX264PresetsUnparsedUTF8String;
}

@property (nonatomic, readwrite) int codec;
@property (nonatomic, readwrite) int qualityType;

@end

@implementation HBVideoController

- (void)setPictureSettingsField:(NSString *)string
{
    if (string)
    {
        [_pictureSettingsField autorelease];
        _pictureSettingsField = [[NSString stringWithFormat:@"Picture Settings: %@", string] retain];
    }
}

- (void)setPictureFiltersField:(NSString *)string
{
    if (string)
    {
        [_pictureFiltersField autorelease];
        _pictureFiltersField = [[NSString stringWithFormat:@"Picture Filters: %@", string] retain];
    }
}

- (NSString *)selectedBitrate
{
    return [fVidBitrateField stringValue];
}

- (NSString *)selectedQuality
{
    return [fVidQualityRFField stringValue];
}


- (instancetype)init
{
    self = [super initWithNibName:@"Video" bundle:nil];
    if (self)
    {
        /*
         * initialize fX264PresetsUnparsedUTF8String as early as possible
         * avoids an invalid free
         */
        fX264PresetsUnparsedUTF8String = NULL;
        _pictureFiltersField = @"Picture Filters:";
        _pictureSettingsField = @"Picture Settings:";

        NSNotificationCenter *center = [NSNotificationCenter defaultCenter];

        /* register that we are interested in changes made to the video container */
        [center addObserver:self selector: @selector(containerChanged:) name:HBContainerChangedNotification object:nil];
        [center addObserver:self selector: @selector(titleChanged:) name:HBTitleChangedNotification object:nil];

        // Observe the andvanced tab preference.
        [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self
                                                                  forKeyPath:@"values.HBShowAdvancedTab"
                                                                     options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial
                                                                     context:NULL];
    }

    return self;
}

- (void)loadView {
    [super loadView];

    /* Video encoder */
    [fVidEncoderPopUp removeAllItems];
    [fVidEncoderPopUp addItemWithTitle:@"H.264 (x264)"];

    /* setup our x264/x265 presets widgets */
    [self switchPresetViewForEncoder:HB_VCODEC_X264];

    /* Video quality */
	[fVidBitrateField setIntValue: 1000];
    self.qualityType = 0;

    [self videoMatrixChanged:nil];

    /* Video framerate */
    [fVidRatePopUp removeAllItems];
    NSMenuItem *menuItem = [[fVidRatePopUp menu] addItemWithTitle:@"Same as source"
                                               action:nil
                                        keyEquivalent:@""];
    [menuItem setTag:hb_video_framerate_get_from_name("Same as source")];
    for (const hb_rate_t *video_framerate = hb_video_framerate_get_next(NULL);
         video_framerate != NULL;
         video_framerate  = hb_video_framerate_get_next(video_framerate))
    {
        NSString *itemTitle;
        if (!strcmp(video_framerate->name, "23.976"))
        {
            itemTitle = @"23.976 (NTSC Film)";
        }
        else if (!strcmp(video_framerate->name, "25"))
        {
            itemTitle = @"25 (PAL Film/Video)";
        }
        else if (!strcmp(video_framerate->name, "29.97"))
        {
            itemTitle = @"29.97 (NTSC Video)";
        }
        else
        {
            itemTitle = @(video_framerate->name);
        }
        menuItem = [[fVidRatePopUp menu] addItemWithTitle:itemTitle
                                                   action:nil
                                            keyEquivalent:@""];
        [menuItem setTag:video_framerate->rate];
    }
    [fVidRatePopUp selectItemAtIndex:0];

    /* We disable the Turbo 1st pass checkbox since we are not x264 */
	[fVidTurboPassCheck setEnabled: NO];
	[fVidTurboPassCheck setState: NSOffState];
}

- (void)setUIEnabled:(BOOL)flag {
    NSControl *controls[] =
    {
        fFramerateMatrix,
        fVidRateField,
        fVidRatePopUp,
        fVidEncoderField,
        fVidEncoderPopUp,
        fVidQualityField,
        fVidQualityRFLabel,
        fVidQualityRFField,
        fVidQualityMatrix,
        fVidBitrateField,
        fVidQualitySlider,
        fVidTwoPassCheck,
        fVidTurboPassCheck,
        fEncoderOptionsLabel,
        fPictureSettingsField,
        fPictureFiltersField
    };

    for (unsigned i = 0; i < (sizeof(controls) / sizeof(NSControl *)); i++)
    {
        if ([[controls[i] className] isEqualToString: @"NSTextField"])
        {
            NSTextField *tf = (NSTextField *)controls[i];
            if (![tf isBezeled])
            {
                [tf setTextColor: (flag ?
                                   [NSColor controlTextColor] :
                                   [NSColor disabledControlTextColor])];
                continue;
            }
        }
        [controls[i] setEnabled:flag];
    }

    [self videoMatrixChanged:nil];
    [self enableEncoderOptionsWidgets:flag];
}

- (void)containerChanged:(NSNotification *)aNotification
{
    NSDictionary *notDict = [aNotification userInfo];

    int videoContainer = [notDict[keyContainerTag] intValue];

    /* lets get the tag of the currently selected item first so we might reset it later */
    int selectedVidEncoderTag = self.codec;
    BOOL encoderSupported = NO;

    /* Note: we now store the video encoder int values from common.c in the tags of each popup for easy retrieval later */
    [fVidEncoderPopUp removeAllItems];
    for (const hb_encoder_t *video_encoder = hb_video_encoder_get_next(NULL);
         video_encoder != NULL;
         video_encoder  = hb_video_encoder_get_next(video_encoder))
    {
        if (video_encoder->muxers & videoContainer)
        {
            NSMenuItem *menuItem = [[fVidEncoderPopUp menu] addItemWithTitle:@(video_encoder->name)
                                                          action:nil
                                                   keyEquivalent:@""];
            [menuItem setTag:video_encoder->codec];

            if (selectedVidEncoderTag == video_encoder->codec)
            {
                encoderSupported = YES;
            }
        }
    }

    if (!encoderSupported)
    {
        self.codec = hb_video_encoder_get_default(videoContainer);
        [self videoEncoderPopUpChanged:nil];
    }
    else
    {
        self.codec = selectedVidEncoderTag;
    }

    [fVidEncoderPopUp selectItemWithTag:self.codec];

}

- (void)titleChanged:(NSNotification *)aNotification
{
    [fVidRatePopUp selectItemAtIndex: 0];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if (context == NULL)
    {
        if ([keyPath isEqualToString:@"values.HBShowAdvancedTab"])
        {
            [self validateAdvancedOptionsCheckBoxForEncoder:self.codec];
        }
    }
    else
    {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

- (void)validateAdvancedOptionsCheckBoxForEncoder:(int)encoder
{
    if ([[NSUserDefaults standardUserDefaults] boolForKey:@"HBShowAdvancedTab"] && (encoder == HB_VCODEC_X264))
    {
        [fX264UseAdvancedOptionsCheck setHidden:NO];
        [fDividerLine setHidden:YES];
        [fEncoderOptionsLabel setStringValue:NSLocalizedString(@"Encoder Options:", @"")];
    }
    else
    {
        [fX264UseAdvancedOptionsCheck setHidden:YES];
        [fDividerLine setHidden:NO];
        [fEncoderOptionsLabel setStringValue:NSLocalizedString(@"Encoder Options", @"")];
    }
}

#pragma mark - apply settings

- (void)applyVideoSettingsFromQueue:(NSDictionary *)queueToApply
{
    /* video encoder */
    self.codec = [queueToApply[@"JobVideoEncoderVcodec"] intValue];

    /* Lets run through the following functions to get variables set there */
    [self videoEncoderPopUpChanged:nil];

    /* advanced x264 options */
    if ([queueToApply[@"x264UseAdvancedOptions"] intValue])
    {
        // we are using the advanced panel
        [self.fAdvancedOptions setOptions:queueToApply[@"x264Option"]];
        // preset does not use the x264 preset system, reset the widgets
        [self setPreset:     nil];
        [self setTune:       nil];
        [self setOptionExtra:queueToApply[@"x264Option"]];
        [self setProfile:    nil];
        [self setLevel:      nil];
        // enable the advanced panel and update the widgets
        [fX264UseAdvancedOptionsCheck setState:NSOnState];
        [self updateEncoderOptionsWidgets:nil];
    }
    else if (self.codec == HB_VCODEC_X264 || self.codec == HB_VCODEC_X265)
    {
        // we are using the x264 preset system
        [self setPreset:     queueToApply[@"VideoPreset"]];
        [self setTune:       queueToApply[@"VideoTune"]];
        [self setOptionExtra:queueToApply[@"VideoOptionExtra"]];
        [self setProfile:    queueToApply[@"VideoProfile"]];
        [self setLevel:      queueToApply[@"VideoLevel"]];
        // preset does not use the advanced panel, reset it
        [self.fAdvancedOptions setOptions:@""];
        // disable the advanced panel and update the widgets
        [fX264UseAdvancedOptionsCheck setState:NSOffState];
        [self updateEncoderOptionsWidgets:nil];
    }
    else
    {
        self.lavcOptions = queueToApply[@"VideoOptionExtra"];
    }

    /* Video quality */
    self.qualityType = [queueToApply[@"VideoQualityType"] intValue] - 1;

    [fVidBitrateField setStringValue:queueToApply[@"VideoAvgBitrate"]];

    int direction;
    float minValue, maxValue, granularity;

    hb_video_quality_get_limits(self.codec, &minValue, &maxValue, &granularity, &direction);

    if (!direction)
    {
        [fVidQualitySlider setFloatValue:[queueToApply[@"VideoQualitySlider"] floatValue]];
    }
    else
    {
        /*
         * Since ffmpeg and x264 use an "inverted" slider (lower values
         * indicate a higher quality) we invert the value on the slider
         */
        [fVidQualitySlider setFloatValue:([fVidQualitySlider minValue] +
                                          [fVidQualitySlider maxValue] -
                                          [queueToApply[@"VideoQualitySlider"] floatValue])];
    }

    [self videoMatrixChanged:nil];

    /* Video framerate */
    if ([queueToApply[@"VideoFramerate"] isEqualToString:@"Same as source"])
    {
        /* Now set the Video Frame Rate Mode to either vfr or cfr according to the preset */
        if ([queueToApply[@"VideoFramerateMode"] isEqualToString:@"vfr"])
        {
            [fFramerateMatrix selectCellAtRow:0 column:0]; // we want vfr
        }
        else
        {
            [fFramerateMatrix selectCellAtRow:1 column:0]; // we want cfr
        }
    }
    else
    {
        /* Now set the Video Frame Rate Mode to either pfr or cfr according to the preset */
        if ([queueToApply[@"VideoFramerateMode"] isEqualToString:@"pfr"])
        {
            [fFramerateMatrix selectCellAtRow:0 column:0]; // we want pfr
        }
        else
        {
            [fFramerateMatrix selectCellAtRow:1 column:0]; // we want cfr
        }
    }
    [fVidRatePopUp selectItemWithTitle:queueToApply[@"VideoFramerate"]];
    [self videoFrameRateChanged:nil];

    /* 2 Pass Encoding */
    [fVidTwoPassCheck setState:[queueToApply[@"VideoTwoPass"] intValue]];
    [self twoPassCheckboxChanged:nil];
    /* Turbo 1st pass for 2 Pass Encoding */
    [fVidTurboPassCheck setState:[queueToApply[@"VideoTurboTwoPass"] intValue]];
}

- (void)applySettingsFromPreset:(NSDictionary *)preset
{
    /* map legacy encoder names via libhb */
    const char *strValue = hb_video_encoder_sanitize_name([preset[@"VideoEncoder"] UTF8String]);

    self.codec = hb_video_encoder_get_from_name(strValue);
    [self videoEncoderPopUpChanged:nil];

    if (self.codec == HB_VCODEC_X264 || self.codec == HB_VCODEC_X265)
    {
        if (self.codec == HB_VCODEC_X264 &&
            (!preset[@"x264UseAdvancedOptions"] ||
            [preset[@"x264UseAdvancedOptions"] intValue]))
        {
            /*
             * x264UseAdvancedOptions is not set (legacy preset)
             * or set to 1 (enabled), so we use the old advanced panel
             */
            if (preset[@"x264Option"])
            {
                /* we set the advanced options string here if applicable */
                [self.fAdvancedOptions setOptions:preset[@"x264Option"]];
                [self setOptionExtra:preset[@"x264Option"]];
            }
            else
            {
                [self.fAdvancedOptions setOptions:@""];
                [self setOptionExtra:nil];
            }
            /* preset does not use the x264 preset system, reset the widgets */
            [self setPreset: nil];
            [self setTune:   nil];
            [self setProfile:nil];
            [self setLevel:  nil];

            /* we enable the advanced panel and update the widgets */
            if ([[NSUserDefaults standardUserDefaults] boolForKey:@"HBShowAdvancedTab"])
            {
                [fX264UseAdvancedOptionsCheck setState:NSOnState];
            }
            else
            {
                [fX264UseAdvancedOptionsCheck setState:NSOffState];
            }
            [self updateEncoderOptionsWidgets:nil];
        }
        else
        {
            // x264UseAdvancedOptions is set to 0 (disabled),
            // so we use the new preset system and
            // disable the advanced panel
            [self.fAdvancedOptions setOptions:@""];
            [fX264UseAdvancedOptionsCheck setState:NSOffState];

            if (preset[@"x264Preset"])
            {
                // Read the old x264 preset keys
                [self setPreset:     preset[@"x264Preset"]];
                [self setTune:       preset[@"x264Tune"]];
                [self setOptionExtra:preset[@"x264OptionExtra"]];
                [self setProfile:    preset[@"h264Profile"]];
                [self setLevel:      preset[@"h264Level"]];
            }
            else
            {
                // Read the new preset keys (0.10)
                [self setPreset:     preset[@"VideoPreset"]];
                [self setTune:       preset[@"VideoTune"]];
                [self setOptionExtra:preset[@"VideoOptionExtra"]];
                [self setProfile:    preset[@"VideoProfile"]];
                [self setLevel:      preset[@"VideoLevel"]];
            }

            [self updateEncoderOptionsWidgets:nil];
        }
    }
    else
    {
        if (preset[@"lavcOption"])
        {
            // Load the old format
            self.lavcOptions = preset[@"lavcOption"];
        }
        else
        {
            self.lavcOptions = preset[@"VideoOptionExtra"];
        }
    }

    int qualityType = [preset[@"VideoQualityType"] intValue] - 1;
    /* Note since the removal of Target Size encoding, the possible values for VideoQuality type are 0 - 1.
     * Therefore any preset that uses the old 2 for Constant Quality would now use 1 since there is one less index
     * for the fVidQualityMatrix. It should also be noted that any preset that used the deprecated Target Size
     * setting of 0 would set us to 0 or ABR since ABR is now tagged 0. Fortunately this does not affect any built-in
     * presets since they all use Constant Quality or Average Bitrate.*/
    if (qualityType == -1)
    {
        qualityType = 0;
    }
    self.qualityType = qualityType;

    [fVidBitrateField setStringValue:preset[@"VideoAvgBitrate"]];

    int direction;
    float minValue, maxValue, granularity;

    hb_video_quality_get_limits(self.codec, &minValue, &maxValue, &granularity, &direction);

    if (!direction)
    {
        [fVidQualitySlider setFloatValue:[preset[@"VideoQualitySlider"] floatValue]];
    }
    else
    {
        /*
         * Since ffmpeg and x264 use an "inverted" slider (lower values
         * indicate a higher quality) we invert the value on the slider
         */
        [fVidQualitySlider setFloatValue:([fVidQualitySlider minValue] +
                                          [fVidQualitySlider maxValue] -
                                          [preset[@"VideoQualitySlider"] floatValue])];
    }

    [self videoMatrixChanged:nil];

    /* Video framerate */
    if ([preset[@"VideoFramerate"] isEqualToString:@"Same as source"])
    {
        /* Now set the Video Frame Rate Mode to either vfr or cfr according to the preset */
        if (!preset[@"VideoFramerateMode"] ||
            [preset[@"VideoFramerateMode"] isEqualToString:@"vfr"])
        {
            [fFramerateMatrix selectCellAtRow:0 column:0]; // we want vfr
        }
        else
        {
            [fFramerateMatrix selectCellAtRow:1 column:0]; // we want cfr
        }
    }
    else
    {
        /* Now set the Video Frame Rate Mode to either pfr or cfr according to the preset */
        if ([preset[@"VideoFramerateMode"] isEqualToString:@"pfr"] ||
            [preset[@"VideoFrameratePFR"]  intValue] == 1)
        {
            [fFramerateMatrix selectCellAtRow:0 column:0]; // we want pfr
        }
        else
        {
            [fFramerateMatrix selectCellAtRow:1 column:0]; // we want cfr
        }
    }
    /* map legacy names via libhb */
    int intValue = hb_video_framerate_get_from_name([preset[@"VideoFramerate"] UTF8String]);
    [fVidRatePopUp selectItemWithTag:intValue];
    [self videoFrameRateChanged:nil];

    /* 2 Pass Encoding */
    [fVidTwoPassCheck setState:[preset[@"VideoTwoPass"] intValue]];
    [self twoPassCheckboxChanged:nil];

    /* Turbo 1st pass for 2 Pass Encoding */
    [fVidTurboPassCheck setState:[preset[@"VideoTurboTwoPass"] intValue]];
}

- (void)prepareVideoForQueueFileJob:(NSMutableDictionary *)queueFileJob
{
    queueFileJob[@"VideoEncoder"] = @(hb_video_encoder_get_name(self.codec));

    /* x264 advanced options */
    if ([fX264UseAdvancedOptionsCheck state])
    {
        // we are using the advanced panel
        queueFileJob[@"x264UseAdvancedOptions"] = @1;
        queueFileJob[@"x264Option"] = [self.fAdvancedOptions optionsString];
    }
    else if (self.codec == HB_VCODEC_X264 || self.codec == HB_VCODEC_X265)
    {
        // we are using the x264/x265 preset system
        queueFileJob[@"x264UseAdvancedOptions"] = @0;
        queueFileJob[@"VideoPreset"] = [self preset];
        queueFileJob[@"VideoTune"] = [self tune];
        queueFileJob[@"VideoOptionExtra"] = [self optionExtra];
        queueFileJob[@"VideoProfile"] = [self profile];
        queueFileJob[@"VideoLevel"] = [self level];
    }
    else
    {
        /* FFmpeg (lavc) Option String */
        queueFileJob[@"VideoOptionExtra"] = self.lavcOptions;
    }

	queueFileJob[@"VideoQualityType"] = @(self.qualityType + 1);
	queueFileJob[@"VideoAvgBitrate"] = [fVidBitrateField stringValue];
	queueFileJob[@"VideoQualitySlider"] = @([fVidQualityRFField floatValue]);
    /* Framerate */
    queueFileJob[@"VideoFramerate"] = [fVidRatePopUp titleOfSelectedItem];
    /* Frame Rate Mode */
    if ([fFramerateMatrix selectedRow] == 1) // if selected we are cfr regardless of the frame rate popup
    {
        queueFileJob[@"VideoFramerateMode"] = @"cfr";
    }
    else
    {
        if ([fVidRatePopUp indexOfSelectedItem] == 0) // Same as source frame rate
        {
            queueFileJob[@"VideoFramerateMode"] = @"vfr";
        }
        else
        {
            queueFileJob[@"VideoFramerateMode"] = @"pfr";
        }

    }

	/* 2 Pass Encoding */
	queueFileJob[@"VideoTwoPass"] = @([fVidTwoPassCheck state]);
	/* Turbo 2 pass Encoding fVidTurboPassCheck*/
	queueFileJob[@"VideoTurboTwoPass"] = @([fVidTurboPassCheck state]);

    /* Video encoder */
	queueFileJob[@"JobVideoEncoderVcodec"] = @(self.codec);

    /* Framerate */
    queueFileJob[@"JobIndexVideoFramerate"] = @([[fVidRatePopUp selectedItem] tag]);
}

- (void)prepareVideoForJobPreview:(hb_job_t *)job andTitle:(hb_title_t *)title
{
    job->vcodec = self.codec;
    job->fastfirstpass = 0;

    job->chapter_markers = 0;

	if (job->vcodec == HB_VCODEC_X264)
    {
        /* advanced x264 options */
        NSString   *tmpString;
        // translate zero-length strings to NULL for libhb
        const char *encoder_preset  = NULL;
        const char *encoder_tune    = NULL;
        const char *encoder_options = NULL;
        const char *encoder_profile = NULL;
        const char *encoder_level   = NULL;
        if ([fX264UseAdvancedOptionsCheck state])
        {
            // we are using the advanced panel
            if ([(tmpString = [self.fAdvancedOptions optionsString]) length])
            {
                encoder_options = [tmpString UTF8String];
            }
        }
        else
        {
            // we are using the x264 preset system
            if ([(tmpString = [self tune]) length])
            {
                encoder_tune = [tmpString UTF8String];
            }
            if ([(tmpString = [self optionExtra]) length])
            {
                encoder_options = [tmpString UTF8String];
            }
            if ([(tmpString = [self profile]) length])
            {
                encoder_profile = [tmpString UTF8String];
            }
            if ([(tmpString = [self level]) length])
            {
                encoder_level = [tmpString UTF8String];
            }
            encoder_preset = [[self preset] UTF8String];
        }
        hb_job_set_encoder_preset (job, encoder_preset);
        hb_job_set_encoder_tune   (job, encoder_tune);
        hb_job_set_encoder_options(job, encoder_options);
        hb_job_set_encoder_profile(job, encoder_profile);
        hb_job_set_encoder_level  (job, encoder_level);
    }
    else if (job->vcodec & HB_VCODEC_FFMPEG_MASK)
    {
        hb_job_set_encoder_options(job, [self.lavcOptions UTF8String]);
    }

    /* Video settings */
    int fps_mode, fps_num, fps_den;
    if( [fVidRatePopUp indexOfSelectedItem] > 0 )
    {
        /* a specific framerate has been chosen */
        fps_num = 27000000;
        fps_den = (int)[[fVidRatePopUp selectedItem] tag];
        if ([fFramerateMatrix selectedRow] == 1)
        {
            // CFR
            fps_mode = 1;
        }
        else
        {
            // PFR
            fps_mode = 2;
        }
    }
    else
    {
        /* same as source */
        fps_num = title->rate;
        fps_den = title->rate_base;
        if ([fFramerateMatrix selectedRow] == 1)
        {
            // CFR
            fps_mode = 1;
        }
        else
        {
            // VFR
            fps_mode = 0;
        }
    }

    switch (self.qualityType)
    {
        case 0:
            /* ABR */
            job->vquality = -1.0;
            job->vbitrate = [fVidBitrateField intValue];
            break;
        case 1:
            /* Constant Quality */
            job->vquality = [fVidQualityRFField floatValue];
            job->vbitrate = 0;
            break;
    }

    /* Add framerate shaping filter */
    hb_filter_object_t *filter = hb_filter_init(HB_FILTER_VFR);
    hb_add_filter(job, filter, [[NSString stringWithFormat:@"%d:%d:%d",
                                 fps_mode, fps_num, fps_den] UTF8String]);
}

- (void)prepareVideoForPreset:(NSMutableDictionary *)preset
{
    preset[@"VideoEncoder"] = @(hb_video_encoder_get_name(self.codec));

    /* x264 Options, this will either be advanced panel or the video tabs x264 presets panel with modded option string */
    if ([fX264UseAdvancedOptionsCheck state] == NSOnState)
    {
        /* use the old advanced panel */
        preset[@"x264UseAdvancedOptions"] = @1;
        preset[@"x264Option"] = [self.fAdvancedOptions optionsString];
    }
    else if (self.codec == HB_VCODEC_X264 || self.codec == HB_VCODEC_X265)
    {
        /* use the x264 preset system */
        preset[@"x264UseAdvancedOptions"] = @0;
        preset[@"VideoPreset"]      = [self preset];
        preset[@"VideoTune"]        = [self tune];
        preset[@"VideoOptionExtra"] = [self optionExtra];
        preset[@"VideoProfile"]     = [self profile];
        preset[@"VideoLevel"]       = [self level];

        /*
         * bonus: set the unparsed options to make the preset compatible
         * with old HB versions
         */
        if (fX264PresetsUnparsedUTF8String != NULL)
        {
            preset[@"x264Option"] = @(fX264PresetsUnparsedUTF8String);
        }
        else
        {
            preset[@"x264Option"] = @"";
        }
    }
    else
    {
        /* FFmpeg (lavc) Option String */
        preset[@"VideoOptionExtra"] = self.lavcOptions;
    }

    /* though there are actually only 0 - 1 types available in the ui we need to map to the old 0 - 2
     * set of indexes from when we had 0 == Target , 1 == Abr and 2 == Constant Quality for presets
     * to take care of any legacy presets. */
    preset[@"VideoQualityType"] = @(self.qualityType +1);
    preset[@"VideoAvgBitrate"] = [fVidBitrateField stringValue];
    preset[@"VideoQualitySlider"] = @([fVidQualityRFField floatValue]);

    /* Video framerate and framerate mode */
    if ([fFramerateMatrix selectedRow] == 1)
    {
        preset[@"VideoFramerateMode"] = @"cfr";
    }
    if ([fVidRatePopUp indexOfSelectedItem] == 0) // Same as source is selected
    {
        preset[@"VideoFramerate"] = @"Same as source";

        if ([fFramerateMatrix selectedRow] == 0)
        {
            preset[@"VideoFramerateMode"] = @"vfr";
        }
    }
    else // translate the rate (selected item's tag) to the official libhb name
    {
        preset[@"VideoFramerate"] = [NSString stringWithFormat:@"%s",
                           hb_video_framerate_get_name((int)[[fVidRatePopUp selectedItem] tag])];

        if ([fFramerateMatrix selectedRow] == 0)
        {
            preset[@"VideoFramerateMode"] = @"pfr";
        }
    }



    /* 2 Pass Encoding */
    preset[@"VideoTwoPass"] = @([fVidTwoPassCheck state]);
    /* Turbo 2 pass Encoding fVidTurboPassCheck*/
    preset[@"VideoTurboTwoPass"] = @([fVidTurboPassCheck state]);
}

#pragma mark - Video

- (IBAction) videoEncoderPopUpChanged: (id) sender
{
    [self switchPresetViewForEncoder:self.codec];

    [[NSNotificationCenter defaultCenter] postNotificationName:HBVideoEncoderChangedNotification object:self];

    [self setupQualitySlider];
	[self twoPassCheckboxChanged: sender];
}

- (IBAction) twoPassCheckboxChanged: (id) sender
{
	/* check to see if x264 is chosen */
	if(self.codec == HB_VCODEC_X264)
    {
		if( [fVidTwoPassCheck state] == NSOnState)
		{
			[fVidTurboPassCheck setHidden: NO];
		}
		else
		{
			[fVidTurboPassCheck setHidden: YES];
			[fVidTurboPassCheck setState: NSOffState];
		}
		/* Make sure Two Pass is checked if Turbo is checked */
		if( [fVidTurboPassCheck state] == NSOnState)
		{
			[fVidTwoPassCheck setState: NSOnState];
		}
	}
	else
	{
		[fVidTurboPassCheck setHidden: YES];
		[fVidTurboPassCheck setState: NSOffState];
	}

	/* We call method method to change UI to reflect whether a preset is used or not*/
	[self.fHBController customSettingUsed: sender];
}

- (IBAction ) videoFrameRateChanged: (id) sender
{
    /* if no valid framerate is selected, use "Same as source" */
    if ([fVidRatePopUp selectedItem] == nil)
    {
        [fVidRatePopUp selectItemAtIndex:0];
    }

    /* Hide and set the PFR Checkbox to OFF if we are set to Same as Source */
    /* Depending on whether or not Same as source is selected modify the title for
     * fFramerateVfrPfrCell*/
    if ([fVidRatePopUp indexOfSelectedItem] == 0) // We are Same as Source
    {
        [fFramerateVfrPfrCell setTitle:@"Variable Framerate"];
    }
    else
    {
        [fFramerateVfrPfrCell setTitle:@"Peak Framerate (VFR)"];


    }

    /* We call method method to change UI to reflect whether a preset is used or not*/
	[self.fHBController customSettingUsed: sender];
}

- (IBAction) videoMatrixChanged: (id) sender;
{
    /* We use the selectedCell: tag of the fVidQualityMatrix instead of selectedRow
     * so that the order of the video controls can be switched around.
     * Constant quality is 1 and Average bitrate is 0 for reference. */
    bool bitrate, quality;
    bitrate = quality = false;
    if( [fVidQualityMatrix isEnabled] )
    {
        switch(self.qualityType)
        {
            case 0:
                bitrate = true;
                break;
            case 1:
                quality = true;
                break;
        }
    }

    [fVidBitrateField     setEnabled: bitrate];
    [fVidQualitySlider    setEnabled: quality];
    [fVidQualityRFField   setEnabled: quality];
    [fVidQualityRFLabel    setEnabled: quality];
    [fVidTwoPassCheck     setEnabled: !quality &&
     [fVidQualityMatrix isEnabled]];
    if( quality )
    {
        [fVidTwoPassCheck setState: NSOffState];
		[fVidTurboPassCheck setHidden: YES];
		[fVidTurboPassCheck setState: NSOffState];
    }

    [self qualitySliderChanged: sender];
	[self.fHBController customSettingUsed: sender];
}

/* Use this method to setup the quality slider for cq/rf values depending on
 * the video encoder selected.
 */
- (void) setupQualitySlider
{
    /*
     * Get the current slider maxValue to check for a change in slider scale
     * later so that we can choose a new similar value on the new slider scale
     */
    float previousMaxValue             = [fVidQualitySlider maxValue];
    float previousPercentOfSliderScale = ([fVidQualitySlider floatValue] /
                                          ([fVidQualitySlider maxValue] -
                                           [fVidQualitySlider minValue] + 1));
    [fVidQualityRFLabel setStringValue:[NSString stringWithFormat:@"%s",
                                        hb_video_quality_get_name(self.codec)]];
    int direction;
    float minValue, maxValue, granularity;
    hb_video_quality_get_limits([self codec],
                                &minValue, &maxValue, &granularity, &direction);
    if (granularity < 1.0f)
    {
        /*
         * Encoders that allow fractional CQ values often have a low granularity
         * which makes the slider hard to use, so use a value from preferences.
         */
        granularity = [[NSUserDefaults standardUserDefaults]
                       floatForKey:@"x264CqSliderFractional"];
    }
    [fVidQualitySlider setMinValue:minValue];
    [fVidQualitySlider setMaxValue:maxValue];
    [fVidQualitySlider setNumberOfTickMarks:((maxValue - minValue) *
                                             (1.0f / granularity)) + 1];

    /* check to see if we have changed slider scales */
    if (previousMaxValue != maxValue)
    {
        /*
         * if so, convert the old setting to the new scale as close as possible
         * based on percentages
         */
        [fVidQualitySlider setFloatValue:((maxValue - minValue + 1.) *
                                          (previousPercentOfSliderScale))];
    }

    [self qualitySliderChanged:nil];
}

- (IBAction) qualitySliderChanged: (id) sender
{
    /*
     * Our constant quality slider is in a range based
     * on each encoders qp/rf values. The range depends
     * on the encoder. Also, the range is inverse of quality
     * for all of the encoders *except* for theora
     * (ie. as the "quality" goes up, the cq or rf value
     * actually goes down). Since the IB sliders always set
     * their max value at the right end of the slider, we
     * will calculate the inverse, so as the slider floatValue
     * goes up, we will show the inverse in the rf field
     * so, the floatValue at the right for x264 would be 51
     * and our rf field needs to show 0 and vice versa.
     */
    int direction;
    float minValue, maxValue, granularity;
    float inverseValue = ([fVidQualitySlider minValue] +
                          [fVidQualitySlider maxValue] -
                          [fVidQualitySlider floatValue]);

    hb_video_quality_get_limits(self.codec, &minValue, &maxValue, &granularity, &direction);

    if (!direction)
    {
        [fVidQualityRFField setStringValue:[NSString stringWithFormat:@"%.2f",
                                            [fVidQualitySlider floatValue]]];
    }
    else
    {
        [fVidQualityRFField setStringValue:[NSString stringWithFormat:@"%.2f",
                                            inverseValue]];
    }
    /* Show a warning if x264 and rf 0 which is lossless */
    if (self.codec == HB_VCODEC_X264 && inverseValue == 0.0)
    {
        [fVidQualityRFField setStringValue:[NSString stringWithFormat:@"%.2f (Warning: Lossless)",
                                            inverseValue]];
    }

    [self.fHBController customSettingUsed: sender];
}

- (IBAction)bitrateFieldChanged:(id)sender
{
    [self.fHBController customSettingUsed: sender];
}

#pragma mark - Video x264/x265 Presets

- (void)switchPresetViewForEncoder:(int)encoder
{
    [self.fAdvancedOptions setHidden:YES];

    if (encoder == HB_VCODEC_X264 || encoder == HB_VCODEC_X265)
    {
        [fPresetsBox setContentView:fPresetView];
        [self setupPresetsWidgetsForEncoder:encoder];

        if (encoder == HB_VCODEC_X264)
        {
            [self.fAdvancedOptions setHidden:NO];
        }
    }
    else if (encoder & HB_VCODEC_FFMPEG_MASK)
    {
        [fPresetsBox setContentView:fSimplePresetView];
    }
    else
    {
        [fPresetsBox setContentView:nil];
    }
}

- (void) setupPresetsWidgetsForEncoder:(int)encoder
{
    [self validateAdvancedOptionsCheckBoxForEncoder:(int)encoder];

    if (encoder == HB_VCODEC_X264)
    {
        [fFastDecodeCheck setHidden:NO];
    }
    else
    {
        [fFastDecodeCheck setHidden:YES];
    }

    NSUInteger i;
    // populate the preset system widgets via hb_video_encoder_get_* functions.
    // store preset names
    const char* const *presets = hb_video_encoder_get_presets(encoder);
    NSMutableArray *tmp_array = [[NSMutableArray alloc] init];
    for (i = 0; presets[i] != NULL; i++)
    {
        [tmp_array addObject:@(presets[i])];
        if (!strcasecmp(presets[i], "medium"))
        {
            fMediumPresetIndex = i;
        }
    }
    [fPresetNames autorelease];
    fPresetNames = [[NSArray alloc] initWithArray:tmp_array];
    [tmp_array release];
    // setup the preset slider
    [fPresetsSlider setMinValue:0];
    [fPresetsSlider setMaxValue:[fPresetNames count]-1];
    [fPresetsSlider setNumberOfTickMarks:[fPresetNames count]];
    [fPresetsSlider setIntegerValue:fMediumPresetIndex];
    [fPresetsSlider setTickMarkPosition:NSTickMarkAbove];
    [fPresetsSlider setAllowsTickMarkValuesOnly:YES];
    [self presetsSliderChanged: nil];
    // setup the tune popup
    [fTunePopUp removeAllItems];
    [fTunePopUp addItemWithTitle: @"none"];
    const char* const *tunes = hb_video_encoder_get_tunes(encoder);
    for (int i = 0; tunes[i] != NULL; i++)
    {
        // we filter out "fastdecode" as we have a dedicated checkbox for it
        if (strcasecmp(tunes[i], "fastdecode") != 0)
        {
            [fTunePopUp addItemWithTitle: @(tunes[i])];
        }
    }
    // the fastdecode checkbox is off by default
    [fFastDecodeCheck setState: NSOffState];
    // setup the h264 profile popup
    [fProfilePopUp removeAllItems];
    const char* const *profiles = hb_video_encoder_get_profiles(encoder);
    for (int i = 0; profiles[i] != NULL; i++)
    {
        [fProfilePopUp addItemWithTitle: @(profiles[i])];
    }
    // setup the level popup
    [fLevelPopUp removeAllItems];
    const char* const *levels = hb_video_encoder_get_levels(encoder);
    for (int i = 0; levels != NULL && levels[i] != NULL; i++)
    {
        [fLevelPopUp addItemWithTitle: @(levels[i])];
    }
    if ([[fLevelPopUp itemArray] count] == 0)
    {
        [fLevelPopUp addItemWithTitle:@"auto"];
    }
    // clear the additional x264 options
    [fDisplayPresetsAdditonalOptionsTextField setStringValue:@""];
}

- (void) enableEncoderOptionsWidgets: (BOOL) enable
{
    NSControl *controls[] =
    {
        fPresetsSlider, fPresetSliderLabel, fPresetSelectedTextField,
        fTunePopUp, fTunePopUpLabel, fFastDecodeCheck,
        fDisplayPresetsAdditonalOptionsTextField, fDisplayPresetsAdditonalOptionsLabel,
        fProfilePopUp, fProfilePopUpLabel,
        fLevelPopUp, fLevelPopUpLabel,
        fDisplayX264PresetsUnparseTextField,
    };

    // check whether the x264 preset system and the advanced panel should be enabled
    BOOL enable_x264_controls  = (enable && [fX264UseAdvancedOptionsCheck state] == NSOffState);
    BOOL enable_advanced_panel = (enable && [fX264UseAdvancedOptionsCheck state] == NSOnState);

    // enable/disable the checkbox and advanced panel
    [fX264UseAdvancedOptionsCheck setEnabled:enable];
    [self.fAdvancedOptions setUIEnabled:enable_advanced_panel];

    // enable/disable the x264 preset system controls
    for (unsigned i = 0; i < (sizeof(controls) / sizeof(NSControl*)); i++)
    {
        if ([[controls[i] className] isEqualToString: @"NSTextField"])
        {
            NSTextField *tf = (NSTextField*)controls[i];
            if (![tf isBezeled])
            {
                [tf setTextColor:(enable_x264_controls       ?
                                  [NSColor controlTextColor] :
                                  [NSColor disabledControlTextColor])];
                continue;
            }
        }
        [controls[i] setEnabled:enable_x264_controls];
    }
}

- (IBAction) updateEncoderOptionsWidgets: (id) sender
{
    if ([fX264UseAdvancedOptionsCheck state] == NSOnState)
    {
        /*
         * we are using or switching to the advanced panel
         *
         * if triggered by selectPreset or applyQueueSettingToMainWindow,
         * the options string will have been specified explicitly - leave it.
         *
         * if triggered by the advanced panel on/off checkbox, set the options
         * string to the value of the unparsed x264 preset system string.
         */
        if (sender == fX264UseAdvancedOptionsCheck)
        {
            if (fX264PresetsUnparsedUTF8String != NULL)
            {
                [self.fAdvancedOptions setOptions:@(fX264PresetsUnparsedUTF8String)];
            }
            else
            {
                [self.fAdvancedOptions setOptions:@""];
            }
        }
    }
    // enable/disable, populate and update the various widgets
    [self             enableEncoderOptionsWidgets:       YES];
    [self             presetsSliderChanged:nil];
    [self.fAdvancedOptions X264AdvancedOptionsSet:  nil];
}

#pragma mark - Lavc presets

- (void) setLavcOptions: (NSString *)optionExtra
{
    if (!optionExtra)
    {
        [fLavcOptionsTextField setStringValue:@""];
        return;
    }
    [fLavcOptionsTextField setStringValue:optionExtra];
}

- (NSString *) lavcOptions
{
    return [fLavcOptionsTextField stringValue];
}

#pragma mark -
#pragma mark x264/x265 preset system

- (NSString *) preset
{
    return (NSString *)fPresetNames[[fPresetsSlider intValue]];
}

- (NSString *) tune
{
    NSString *tune = @"";
    if ([fTunePopUp indexOfSelectedItem])
    {
        tune = [tune stringByAppendingString:
                    [fTunePopUp titleOfSelectedItem]];
    }
    if ([fFastDecodeCheck state])
    {
        if ([tune length])
        {
            tune = [tune stringByAppendingString: @","];
        }
        tune = [tune stringByAppendingString: @"fastdecode"];
    }
    return tune;
}

- (NSString *) optionExtra
{
    return [fDisplayPresetsAdditonalOptionsTextField stringValue];
}

- (NSString *) profile
{
    if ([fProfilePopUp indexOfSelectedItem])
    {
        return [fProfilePopUp titleOfSelectedItem];
    }
    return @"";
}

- (NSString *) level
{
    if ([fLevelPopUp indexOfSelectedItem])
    {
        return [fLevelPopUp titleOfSelectedItem];
    }
    return @"";
}

- (void) setPreset: (NSString *)preset
{
    if (preset)
    {
        for (NSString *name in fPresetNames)
        {
            if ([name isEqualToString:preset])
            {
                [fPresetsSlider setIntegerValue:
                 [fPresetNames indexOfObject:name]];
                return;
            }
        }
    }
    [fPresetsSlider setIntegerValue:fMediumPresetIndex];
}

- (void) setTune: (NSString *)tune
{
    if (!tune)
    {
        [fTunePopUp selectItemAtIndex:0];
        [fFastDecodeCheck setState:NSOffState];
        return;
    }
    // handle fastdecode
    if ([tune rangeOfString:@"fastdecode"].location != NSNotFound)
    {
        [fFastDecodeCheck setState:NSOnState];
    }
    else
    {
        [fFastDecodeCheck setState:NSOffState];
    }
    // filter out fastdecode
    tune = [tune stringByReplacingOccurrencesOfString:@","
                                                   withString:@""];
    tune = [tune stringByReplacingOccurrencesOfString:@"fastdecode"
                                                   withString:@""];
    // set the tune
    [fTunePopUp selectItemWithTitle:tune];
    // fallback
    if ([fTunePopUp indexOfSelectedItem] == -1)
    {
        [fTunePopUp selectItemAtIndex:0];
    }
}

- (void) setOptionExtra: (NSString *)optionExtra
{
    if (!optionExtra)
    {
        [fDisplayPresetsAdditonalOptionsTextField setStringValue:@""];
        return;
    }
    [fDisplayPresetsAdditonalOptionsTextField setStringValue:optionExtra];
}

- (void) setProfile: (NSString *)profile
{
    if (!profile)
    {
        [fProfilePopUp selectItemAtIndex:0];
        return;
    }
    // set the profile
    [fProfilePopUp selectItemWithTitle:profile];
    // fallback
    if ([fProfilePopUp indexOfSelectedItem] == -1)
    {
        [fProfilePopUp selectItemAtIndex:0];
    }
}

- (void) setLevel: (NSString *)level
{
    if (!level)
    {
        [fLevelPopUp selectItemAtIndex:0];
        return;
    }
    // set the level
    [fLevelPopUp selectItemWithTitle:level];
    // fallback
    if ([fLevelPopUp indexOfSelectedItem] == -1)
    {
        [fLevelPopUp selectItemAtIndex:0];
    }
}

- (IBAction) presetsSliderChanged: (id) sender
{
    // we assume the preset names and slider were setup properly
    [fPresetSelectedTextField setStringValue: [self preset]];

    if (self.codec == HB_VCODEC_X264)
    {
        [self x264PresetsChangedDisplayExpandedOptions:nil];
        [fDisplayX264PresetsUnparseTextField setHidden:NO];
    }
    else
    {
        [fDisplayX264PresetsUnparseTextField setHidden:YES];
    }

}

/* This is called everytime a x264 widget in the video tab is changed to
 display the expanded options in a text field via outlet fDisplayX264PresetsUnparseTextField
 */
- (IBAction) x264PresetsChangedDisplayExpandedOptions: (id) sender

{
    if (self.codec != HB_VCODEC_X264)
    {
        return;
    }

    /* API reference:
     *
     * char * hb_x264_param_unparse(const char *x264_preset,
     *                              const char *x264_tune,
     *                              const char *x264_encopts,
     *                              const char *h264_profile,
     *                              const char *h264_level,
     *                              int width, int height);
     */
    NSString   *tmpString;
    const char *x264_preset   = [[self preset] UTF8String];
    const char *x264_tune     = NULL;
    const char *advanced_opts = NULL;
    const char *h264_profile  = NULL;
    const char *h264_level    = NULL;
    int         width         = 1;
    int         height        = 1;
    // prepare the tune, advanced options, profile and level
    if ([(tmpString = [self tune]) length])
    {
        x264_tune = [tmpString UTF8String];
    }
    if ([(tmpString = [self optionExtra]) length])
    {
        advanced_opts = [tmpString UTF8String];
    }
    if ([(tmpString = [self profile]) length])
    {
        h264_profile = [tmpString UTF8String];
    }
    if ([(tmpString = [self level]) length])
    {
        h264_level = [tmpString UTF8String];
    }
    // width and height must be non-zero
    if (_fPresetsWidthForUnparse && _fPresetsHeightForUnparse)
    {
        width  = (int)_fPresetsWidthForUnparse;
        height = (int)_fPresetsHeightForUnparse;
    }
    // free the previous unparsed string
    free(fX264PresetsUnparsedUTF8String);
    // now, unparse
    fX264PresetsUnparsedUTF8String = hb_x264_param_unparse(x264_preset,
                                                           x264_tune,
                                                           advanced_opts,
                                                           h264_profile,
                                                           h264_level,
                                                           width, height);
    // update the text field
    if (fX264PresetsUnparsedUTF8String != NULL)
    {
        [fDisplayX264PresetsUnparseTextField setStringValue:
         [NSString stringWithFormat:@"x264 Unparse: %s",
          fX264PresetsUnparsedUTF8String]];
    }
    else
    {
        [fDisplayX264PresetsUnparseTextField setStringValue:@"x264 Unparse:"];
    }
}

@end
