public static AtemState Build(IBMDSwitcher switcher, AtemStateBuilderSettings updateSettings)
        {
            var state = new AtemState();

            switcher.GetProductName(out string productName);
            state.Info.ProductName = productName;
            switcher.GetVideoMode(out _BMDSwitcherVideoMode videoMode);
            state.Settings.VideoMode = AtemEnumMaps.VideoModesMap.FindByValue(videoMode);
#if !ATEM_v8_1
            switcher.GetTimeCodeMode(out _BMDSwitcherTimeCodeMode timeCodeMode);
            if (timeCodeMode != 0)
            {
                state.Settings.TimeCodeMode = AtemEnumMaps.TimeCodeModeMap.FindByValue(timeCodeMode);
            }
#endif
            switcher.GetTimeCodeLocked(out int locked);
            state.Info.TimecodeLocked = locked != 0;
            switcher.GetTimeCode(out byte hours, out byte minutes, out byte seconds, out byte frames, out int dropFrame);
            state.Info.LastTimecode = new Timecode
            {
                Hour      = hours,
                Minute    = minutes,
                Second    = seconds,
                Frame     = frames,
                DropFrame = dropFrame != 0
            };

            switcher.GetAreOutputsConfigurable(out int configurable);
            state.Info.OnlyConfigurableOutputs = configurable != 0;
            state.Info.HasCameraControl        = switcher is IBMDSwitcherCameraControl;
            state.Info.AdvancedChromaKeyers    = MixEffectStateBuilder.SupportsAdvancedChromaKeyers(switcher);

            try
            {
                switcher.Get3GSDIOutputLevel(out _BMDSwitcher3GSDIOutputLevel outputLevel);
                state.Settings.SDI3GLevel = AtemEnumMaps.SDI3GOutputLevelMap.FindByValue(outputLevel);
            }
            catch (Exception)
            {
                // This call fails on models which dont do 3g sdi
                state.Settings.SDI3GLevel = 0;
            }
            try
            {
                switcher.GetSuperSourceCascade(out int cascade);
                state.Settings.SuperSourceCascade = cascade != 0;
            }
            catch (Exception)
            {
                // This call fails on models which dont have multiple ssrc
                state.Settings.SuperSourceCascade = false;
            }

            switcher.GetPowerStatus(out _BMDSwitcherPowerStatus powerStatus);
            state.Power = new[]
            {
                powerStatus.HasFlag(_BMDSwitcherPowerStatus.bmdSwitcherPowerStatusSupply1),
                powerStatus.HasFlag(_BMDSwitcherPowerStatus.bmdSwitcherPowerStatusSupply2),
            };

            bool supportsDownConvert = true;
            bool supportsMultiviewer = true;

            var modes    = new List <VideoModeInfo>();
            var allModes = Enum.GetValues(typeof(_BMDSwitcherVideoMode)).OfType <_BMDSwitcherVideoMode>().ToArray();
            foreach (_BMDSwitcherVideoMode mode in allModes)
            {
                switcher.DoesSupportVideoMode(mode, out int supported);
                if (supported == 0)
                {
                    continue;
                }

                switcher.DoesVideoModeChangeRequireReconfiguration(mode, out int requiresReconfig);

                var multiviewModes = new List <VideoMode>();
                if (supportsMultiviewer)
                {
                    try
                    {
                        foreach (_BMDSwitcherVideoMode mvMode in allModes)
                        {
                            switcher.DoesSupportMultiViewVideoMode(mode, mvMode, out int mvSupported);
                            if (mvSupported != 0)
                            {
                                multiviewModes.Add(AtemEnumMaps.VideoModesMap.FindByValue(mvMode));
                            }
                        }
                    }
                    catch (NotImplementedException)
                    {
                        supportsMultiviewer = false;
                    }
                }

                var downConvertModes = new List <VideoMode>();
                if (supportsDownConvert)
                {
                    try
                    {
                        foreach (_BMDSwitcherVideoMode dcMode in allModes)
                        {
                            switcher.DoesSupportDownConvertedHDVideoMode(mode, dcMode, out int convertSupported);
                            if (convertSupported != 0)
                            {
                                downConvertModes.Add(AtemEnumMaps.VideoModesMap.FindByValue(dcMode));
                            }
                        }
                    }
                    catch (NotImplementedException)
                    {
                        supportsDownConvert = false;
                    }
                }

                modes.Add(new VideoModeInfo
                {
                    Mode             = AtemEnumMaps.VideoModesMap.FindByValue(mode),
                    RequiresReconfig = requiresReconfig != 0,
                    MultiviewModes   = multiviewModes.ToArray(),
                    DownConvertModes = downConvertModes.ToArray(),
                });
            }
            state.Info.SupportedVideoModes = modes.OrderBy(s => s.Mode).ToList();

            if (supportsMultiviewer)
            {
                foreach (VideoModeInfo mode in state.Info.SupportedVideoModes)
                {
                    switcher.GetMultiViewVideoMode(AtemEnumMaps.VideoModesMap[mode.Mode],
                                                   out _BMDSwitcherVideoMode mvMode);
                    state.Settings.MultiviewVideoModes[mode.Mode] = AtemEnumMaps.VideoModesMap.FindByValue(mvMode);
                }
            }
            if (supportsDownConvert)
            {
                foreach (VideoModeInfo mode in state.Info.SupportedVideoModes)
                {
                    switcher.GetDownConvertedHDVideoMode(AtemEnumMaps.VideoModesMap[mode.Mode],
                                                         out _BMDSwitcherVideoMode dcMode);
                    state.Settings.DownConvertVideoModes[mode.Mode] = AtemEnumMaps.VideoModesMap.FindByValue(dcMode);
                }
            }

            try
            {
                switcher.GetMethodForDownConvertedSD(out _BMDSwitcherDownConversionMethod downConvertMethod);
                state.Settings.DownConvertMode = AtemEnumMaps.SDDownconvertModesMap.FindByValue(downConvertMethod);
            }
            catch (Exception)
            {
                // Not supported
            }

            switcher.DoesSupportAutoVideoMode(out int autoModeSupported);
            state.Info.SupportsAutoVideoMode = autoModeSupported != 0;
            if (state.Info.SupportsAutoVideoMode)
            {
                switcher.GetAutoVideoMode(out int autoVideoMode);
                state.Settings.AutoVideoMode = autoVideoMode != 0;
                switcher.GetAutoVideoModeDetected(out int detected);
                state.Settings.DetectedVideoMode = detected != 0;
            }

            DveInfo(state, switcher);

            SourceStateBuilder.Build(state, switcher);
            Hyperdecks(state, switcher);
            SerialPorts(state, switcher);
            Macros(state.Macros, switcher);
            MediaPoolStateBuilder.Build(state.MediaPool, switcher);
            MixMinusOutputs(state, switcher);
            StreamingStateBuilder.Build(state, switcher);
            RecordingStateBuilder.Build(state, switcher);

            state.DownstreamKeyers = DownstreamKeyerStateBuilder.Build(switcher);
            state.MediaPlayers     = MediaPlayerStateBuilder.Build(switcher, updateSettings, state.MediaPool.Clips.Count > 0);
            state.MixEffects       = MixEffectStateBuilder.Build(switcher);
            MultiViewerStateBuilder.Build(switcher, state);

            if (switcher is IBMDSwitcherFairlightAudioMixer fairlight)
            {
                state.Fairlight = FairlightAudioMixerStateBuilder.Build(fairlight);
            }
            if (switcher is IBMDSwitcherAudioMixer audioMixer)
            {
                state.Audio = AudioStateBuilder.Build(audioMixer);
            }

#if !ATEM_v8_1
            TalkbackStateBuilder.Build(state, switcher);
#endif

            if (switcher is IBMDSwitcherCameraControl camera)
            {
                CameraControlBuilder.Build(state, camera, updateSettings);
            }

            return(state);
        }