using System;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Serialization;

#if UNITY_EDITOR
using UnityEditor;
#endif

namespace FMODUnity
{
    public class PlatformCallbackHandler : ScriptableObject
    {
        // A hook for custom initialization logic. RuntimeManager.Initialize calls this
        // just before calling system.Initialize.
        // Call reportResult() with the result of each FMOD call to use FMOD's error handling logic.
        public virtual void PreInitialize(FMOD.Studio.System system, Action<FMOD.RESULT, string> reportResult)
        {
        }
    }

    // This class holds per-platform settings and provides hooks for platform-specific behaviour.
    // Each platform has a parent platform, forming a hierarchy that is rooted at PlatformDefault.
    // By default a platform inherits all of its properties from its parent platform; this behaviour
    // can be overridden for each property.
    //
    // There is at least one concrete derived class for each supported platform; these classes use
    // [InitializeOnLoad] and a static constructor to register themselves as supported platforms by
    // calling Settings.AddPlatformTemplate. The user can also create instances of the PlatformGroup
    // class and use them to group platforms that have settings in common.
    public abstract class Platform : ScriptableObject
    {
        internal const float DefaultPriority = 0;

#if UNITY_EDITOR
        internal const int MaximumCoreCount = 16;

        internal static readonly FileLayout[] OldFileLayouts = {
            FileLayout.Release_1_10,
            FileLayout.Release_2_0,
            FileLayout.Release_2_1,
        };
#endif

        // These need to match the function called by LoadStaticPlugins
        internal const string RegisterStaticPluginsClassName = "StaticPluginManager";
        internal const string RegisterStaticPluginsFunctionName = "Register";

        // This is a persistent identifier. It is used:
        // * To link platforms together at load time
        // * To avoid creating duplicate platforms from templates (in Settings.OnEnable)
        // * As a key for SettingsEditor UI state
        // It should be kept stable for concrete platforms (like PlatformWindows) to support
        // settings migration in the future.
        [SerializeField]
        private string identifier;

        [SerializeField]
        private string parentIdentifier;

        [SerializeField]
        private bool active = false;

        [SerializeField]
        protected PropertyStorage Properties = new PropertyStorage();

        [SerializeField]
        [FormerlySerializedAs("outputType")]
        internal string OutputTypeName;

        private static List<ThreadAffinityGroup> StaticThreadAffinities = new List<ThreadAffinityGroup>();

        [SerializeField]
        private PropertyThreadAffinityList threadAffinities = new PropertyThreadAffinityList();

#if UNITY_EDITOR
        [SerializeField]
        private float displaySortOrder;

        [SerializeField]
        private List<string> childIdentifiers = new List<string>();
#else
        // The parent platform from which this platform inherits its property values.
        [NonSerialized]
        public Platform Parent;
#endif

        internal string Identifier
        {
            get
            {
                return identifier;
            }

            set
            {
                identifier = value;
            }
        }

        // The display name to show for this platform in the UI.
        internal abstract string DisplayName { get; }

        // Declares the Unity RuntimePlatforms this platform implements.
        internal abstract void DeclareRuntimePlatforms(Settings settings);

#if UNITY_EDITOR
        // The Unity BuildTargets this platform implements.
        // Returns BuildTarget.NoTarget if the correct value is not defined, as some BuildTarget
        // values are only defined in specific circumstances (e.g. Stadia required Unity 2019.3).
        internal abstract IEnumerable<BuildTarget> GetBuildTargets();

        // The old FMOD platform identifier that this platform corresponds to, for settings migration.
        internal abstract Legacy.Platform LegacyIdentifier { get; }
#endif

        // The priority to use when finding a platform to support the current Unity runtime
        // platform (higher priorities are tried first).
        internal virtual float Priority { get { return DefaultPriority; } }

        // Determines whether this platform matches the current environment. When more than one
        // platform implements the current Unity runtime platform, FMOD for Unity will use the
        // highest-priority platform that returns true from MatchesCurrentEnvironment.
        internal virtual bool MatchesCurrentEnvironment { get { return true; } }

        // Whether this platform is a fixed part of the FMOD for Unity settings, or can be
        // added/removed by the user.
        internal virtual bool IsIntrinsic { get { return false; } }

        // A hook for platform-specific initialization logic. RuntimeManager.Initialize calls this
        // before calling FMOD.Studio.System.create.
        internal virtual void PreSystemCreate(Action<FMOD.RESULT, string> reportResult)
        {
        }

        // A hook for platform-specific initialization logic. RuntimeManager.Initialize calls this
        // just before calling studioSystem.Initialize.
        internal virtual void PreInitialize(FMOD.Studio.System studioSystem)
        {
        }

        // The folder in which FMOD .bank files are stored. Used when loading banks.
        internal virtual string GetBankFolder()
        {
            return Application.streamingAssetsPath;
        }

#if UNITY_EDITOR
        [Flags]
        public enum BinaryType
        {
            Release = 1,
            Logging = 2,
            Optional = 4,
            AllVariants = 8,
            All = Release | Logging | Optional | AllVariants
        }

        protected virtual IEnumerable<string> GetBinaryPaths(BuildTarget buildTarget, BinaryType binaryType, string prefix)
        {
            foreach (BinaryFileInfo info in GetBinaryFileInfo(buildTarget, binaryType))
            {
                yield return info.LatestLocation();
            }
        }

        internal abstract class FileInfo
        {
            public FileInfo(FileRecord fileRecord, BinaryType type)
            {
                this.fileRecord = fileRecord;
                this.type = type;
            }

            public readonly BinaryType type;

            private readonly FileRecord fileRecord;

            public string LatestLocation()
            {
                return GetLocation(FileLayout.Latest);
            }

            public IEnumerable<string> OldLocations()
            {
                foreach (FileLayout layout in OldFileLayouts)
                {
                    string location = GetLocation(layout);

                    if (location != null)
                    {
                        yield return location;
                    }
                }
            }

            private string GetLocation(FileLayout layout)
            {
                string basePath = GetBasePath(layout);

                if (basePath == null)
                {
                    return null;
                }

                bool absolute;
                string path;
                fileRecord.GetPath(layout, out path, out absolute);

                if (absolute)
                {
                    return path;
                }
                else
                {
                    return string.Format("{0}/{1}", basePath, path);
                }
            }

            protected abstract string GetBasePath(FileLayout layout);
        }

        internal class BinaryFileInfo : FileInfo
        {
            public BinaryFileInfo(Platform platform, FileRecord fileRecord, BuildTarget buildTarget, BinaryType type)
                : base(fileRecord, type)
            {
                this.platform = platform;
                this.buildTarget = buildTarget;
            }

            private readonly Platform platform;
            private readonly BuildTarget buildTarget;

            protected override string GetBasePath(FileLayout layout)
            {
                BinaryAssetFolderInfo info = platform.GetBinaryAssetFolder(buildTarget);

                if (layout < info.oldestLayout)
                {
                    return null;
                }

                switch (layout)
                {
                    case FileLayout.Release_1_10:
                        return info.path_1_10;
                    case FileLayout.Release_2_0:
                        return string.Format("Assets/Plugins/FMOD/lib/{0}", info.baseName);
                    case FileLayout.Release_2_1:
                    case FileLayout.Release_2_2:
                        return $"{RuntimeUtils.PluginBasePath}/platforms/{info.baseName}/lib";
                    default:
                        throw new ArgumentException("Unrecognised file layout: " + layout);
                }
            }
        }

        public struct FileRecord
        {
            public FileRecord(string latestPath)
            {
                this.latestPath = latestPath;
                pathVersions = null;
            }

            public FileRecord WithAbsoluteVersion(FileLayout layout, string path)
            {
                AddVersion(layout, path, true);
                return this;
            }

            public FileRecord WithRelativeVersion(FileLayout layout, string path)
            {
                AddVersion(layout, path, false);
                return this;
            }

            private void AddVersion(FileLayout layout, string path, bool absolute)
            {
                if (pathVersions == null)
                {
                    pathVersions = new Dictionary<FileLayout, PathInfo>();
                }

                pathVersions.Add(layout, new PathInfo() { path = path, absolute = absolute });
            }

            public void GetPath(FileLayout layout, out string path, out bool absolute)
            {
                if (pathVersions != null)
                {
                    PathInfo pathForLayout;

                    if (pathVersions.TryGetValue(layout, out pathForLayout))
                    {
                        absolute = pathForLayout.absolute;
                        path = pathForLayout.path;
                        return;
                    }
                }

                absolute = false;
                path = latestPath;
            }

            private struct PathInfo
            {
                public string path;
                public bool absolute;
            }

            private readonly string latestPath;
            private Dictionary<FileLayout, PathInfo> pathVersions;
        }

        internal IEnumerable<BinaryFileInfo> GetBinaryFileInfo(BuildTarget buildTarget, BinaryType binaryType)
        {
            bool allVariants = (binaryType & BinaryType.AllVariants) == BinaryType.AllVariants;

            if ((binaryType & BinaryType.Release) == BinaryType.Release)
            {
                foreach (FileRecord record in GetBinaryFiles(buildTarget, allVariants, ""))
                {
                    yield return CreateFileInfo(record, buildTarget, BinaryType.Release);
                }
            }

            if ((binaryType & BinaryType.Logging) == BinaryType.Logging)
            {
                foreach (FileRecord record in GetBinaryFiles(buildTarget, allVariants, "L"))
                {
                    yield return CreateFileInfo(record, buildTarget, BinaryType.Logging);
                }
            }

            if ((binaryType & BinaryType.Optional) == BinaryType.Optional)
            {
                foreach (FileRecord record in GetOptionalBinaryFiles(buildTarget, allVariants))
                {
                    yield return CreateFileInfo(record, buildTarget, BinaryType.Optional);
                }
            }
        }

        internal class SourceFileInfo : FileInfo
        {
            public SourceFileInfo(Platform platform, FileRecord fileRecord)
                : base(fileRecord, BinaryType.Optional)
            {
                this.platform = platform;
            }

            private readonly Platform platform;

            protected override string GetBasePath(FileLayout layout)
            {
                BinaryAssetFolderInfo info = platform.GetBinaryAssetFolder(platform.GetBuildTargets().First());

                if (layout < info.oldestLayout)
                {
                    return null;
                }

                switch (layout)
                {
                    case FileLayout.Release_1_10:
                        return "Plugins/FMOD/Wrapper";
                    case FileLayout.Release_2_0:
                        return "Plugins/FMOD/src/Runtime/wrapper";
                    case FileLayout.Release_2_1:
                    case FileLayout.Release_2_2:
                        return $"{RuntimeUtils.PluginBasePath}/platforms/{info.baseName}/src";
                    default:
                        throw new ArgumentException("Unrecognised file layout: " + layout);
                }
            }
        }

        internal IEnumerable<SourceFileInfo> GetSourceFileInfo()
        {
            foreach (FileRecord record in GetSourceFiles())
            {
                yield return new SourceFileInfo(this, record);
            }
        }

        internal BinaryFileInfo CreateFileInfo(FileRecord record, BuildTarget buildTarget, BinaryType binaryType)
        {
            return new BinaryFileInfo(this, record, buildTarget, binaryType);
        }

        internal virtual IEnumerable<string> GetObsoleteAssetPaths()
        {
            foreach (string path in GetObsoleteFiles())
            {
                yield return $"{RuntimeUtils.PluginBasePath}/{path}";
                yield return $"{RuntimeUtils.PluginBasePathDefault}/{path}";
            }
        }

        // Called by Settings.CanBuildTarget to get the required binaries for the current
        // build target and logging state.
        internal virtual IEnumerable<string> GetBinaryFilePaths(BuildTarget buildTarget, BinaryType binaryType)
        {
            return GetBinaryPaths(buildTarget, binaryType, RuntimeUtils.PluginBasePath);
        }

        // Called by Settings.SelectBinaries to get:
        // * The required and optional binaries for the current build target and logging state;
        //   these get enabled.
        // * All binaries; any that weren't enabled in the previous step get disabled.
        internal virtual IEnumerable<string> GetBinaryAssetPaths(BuildTarget buildTarget, BinaryType binaryType)
        {
            return GetBinaryPaths(buildTarget, binaryType, RuntimeUtils.PluginBasePath);
        }

        public enum FileLayout : uint
        {
            Release_1_10,
            Release_2_0,
            Release_2_1,
            Release_2_2,
            Latest = Release_2_2,
        }

        protected class BinaryAssetFolderInfo
        {
            public BinaryAssetFolderInfo(string baseName, string path_1_10)
            {
                this.baseName = baseName;
                this.path_1_10 = path_1_10;
                this.oldestLayout = FileLayout.Release_1_10;
            }

            public BinaryAssetFolderInfo(string baseName, FileLayout oldestLayout)
            {
                this.baseName = baseName;
                this.path_1_10 = null;
                this.oldestLayout = oldestLayout;
            }

            public string baseName { get; private set; }
            public string path_1_10 { get; private set; }
            public FileLayout oldestLayout { get; private set; }
        }

        protected abstract BinaryAssetFolderInfo GetBinaryAssetFolder(BuildTarget buildTarget);

        protected abstract IEnumerable<FileRecord> GetBinaryFiles(BuildTarget buildTarget, bool allVariants, string suffix);

        protected virtual IEnumerable<FileRecord> GetOptionalBinaryFiles(BuildTarget buildTarget, bool allVariants)
        {
            yield break;
        }

        protected virtual IEnumerable<FileRecord> GetSourceFiles()
        {
            yield break;
        }

        protected virtual IEnumerable<string> GetObsoleteFiles()
        {
            yield break;
        }

        internal virtual bool IsFMODStaticallyLinked { get { return false; } }

        internal virtual bool SupportsAdditionalCPP(BuildTarget target)
        {
            return true;
        }
#endif

        // The base path for FMOD plugins when in a standalone player.
        protected virtual string GetPluginBasePath()
        {
            return string.Format("{0}/Plugins", Application.dataPath);
        }

        // Returns the full path for an FMOD plugin.
        internal virtual string GetPluginPath(string pluginName)
        {
            throw new NotImplementedException(string.Format("Plugins are not implemented on platform {0}", Identifier));
        }

        // Loads static and dynamic FMOD plugins for this platform.
        internal virtual void LoadPlugins(FMOD.System coreSystem, Action<FMOD.RESULT, string> reportResult)
        {
            LoadDynamicPlugins(coreSystem, reportResult);
            LoadStaticPlugins(coreSystem, reportResult);
        }

        // Loads dynamic FMOD plugins for this platform.
        internal virtual void LoadDynamicPlugins(FMOD.System coreSystem, Action<FMOD.RESULT, string> reportResult)
        {
            List<string> pluginNames = Plugins;

            if (pluginNames == null)
            {
                return;
            }

            foreach (string pluginName in pluginNames)
            {
                if (string.IsNullOrEmpty(pluginName))
                {
                    continue;
                }

                string pluginPath = GetPluginPath(pluginName);
                uint handle;

                FMOD.RESULT result = coreSystem.loadPlugin(pluginPath, out handle);

                if (result == FMOD.RESULT.ERR_FILE_BAD || result == FMOD.RESULT.ERR_FILE_NOTFOUND)
                {
                    if (Environment.Is64BitProcess)
                    {
                        // Add a "64" suffix and try again
                        string pluginPath64 = GetPluginPath(pluginName + "64");
                        result = coreSystem.loadPlugin(pluginPath64, out handle);
                    }
                }

                reportResult(result, string.Format("Loading plugin '{0}' from '{1}'", pluginName, pluginPath));
            }
        }

        // Loads static FMOD plugins for this platform.
        internal virtual void LoadStaticPlugins(FMOD.System coreSystem, Action<FMOD.RESULT, string> reportResult)
        {
            if (StaticPlugins.Count > 0)
            {
#if !UNITY_EDITOR && ENABLE_IL2CPP
                // We use reflection here to avoid compile errors if the plugin registration code doesn't exist.
                // It should be generated by Settings.PreprocessStaticPlugins(), which is called from
                // IPreprocessBuildWithReport.OnPreprocessBuild(). However, some compilation scenarios
                // (such as AddressableAssetSettings.BuildPlayerContent()) don't call OnPreprocessBuild(),
                // so we can't generate the plugin registration code.

                string className = string.Format("FMODUnity.{0}", RegisterStaticPluginsClassName);
                Type type = Type.GetType(className);

                if (type == null)
                {
                    RuntimeUtils.DebugLogWarningFormat(
                        "FMOD: {0} static plugins specified, but the {1} class was not found.",
                        StaticPlugins.Count, className);
                    return;
                }

                MethodInfo method = type.GetMethod(RegisterStaticPluginsFunctionName,
                    BindingFlags.Public | BindingFlags.Static);

                if (method == null)
                {
                    RuntimeUtils.DebugLogWarningFormat(
                        "FMOD: {0} static plugins specified, but the {1}.{2} method was not found.",
                        StaticPlugins.Count, className, RegisterStaticPluginsFunctionName);
                    return;
                }

                method.Invoke(null, new object[] { coreSystem, reportResult });
#else
                RuntimeUtils.DebugLogWarningFormat(
                    "FMOD: {0} static plugins specified, but static plugins are only supported on the IL2CPP scripting backend",
                    StaticPlugins.Count);
#endif
            }
        }

        // Ensures that this platform has properties.
        internal void AffirmProperties()
        {
            if (!active)
            {
                Properties = new PropertyStorage();
                InitializeProperties();
                active = true;
            }
        }

        // Clears this platform's properties.
        internal void ClearProperties()
        {
            if (active)
            {
                Properties = new PropertyStorage();
                active = false;
#if UNITY_EDITOR
                DisplaySortOrder = 0;
#endif
            }
        }

        // Initializes this platform's properties to their default values.
        internal virtual void InitializeProperties()
        {
            if (!IsIntrinsic)
            {
                ParentIdentifier = PlatformDefault.ConstIdentifier;
            }
        }

        // Ensures that this platform's properties are valid after loading from file.
        internal virtual void EnsurePropertiesAreValid()
        {
            if (!IsIntrinsic && string.IsNullOrEmpty(ParentIdentifier))
            {
                ParentIdentifier = PlatformDefault.ConstIdentifier;
            }
        }

        internal string ParentIdentifier
        {
            get
            {
                return parentIdentifier;
            }

            set
            {
                parentIdentifier = value;
            }
        }

#if UNITY_EDITOR
        internal float DisplaySortOrder
        {
            get
            {
                return displaySortOrder;
            }

            set
            {
                displaySortOrder = value;
            }
        }
#endif

        internal bool IsLiveUpdateEnabled
        {
            get
            {
#if DEVELOPMENT_BUILD || UNITY_EDITOR
                return LiveUpdate != TriStateBool.Disabled;
#else
                return LiveUpdate == TriStateBool.Enabled;
#endif
            }
        }

        internal bool IsOverlayEnabled
        {
            get
            {
#if DEVELOPMENT_BUILD || UNITY_EDITOR
                return Overlay != TriStateBool.Disabled;
#else
                return Overlay == TriStateBool.Enabled;
#endif
            }
        }

        // A property value that can be inherited from the parent or overridden.
        public class Property<T>
        {
            public T Value;
            public bool HasValue;
        }

        // These stub classes are needed because Unity can't serialize generic classes
        [Serializable]
        public class PropertyBool : Property<TriStateBool>
        {
        }

        [Serializable]
        public class PropertyScreenPosition : Property<ScreenPosition>
        {
        }

        [Serializable]
        public class PropertyInt : Property<int>
        {
        }

        [Serializable]
        public class PropertySpeakerMode : Property<FMOD.SPEAKERMODE>
        {
        }

        [Serializable]
        public class PropertyString : Property<string>
        {
        }

        [Serializable]
        public class PropertyStringList : Property<List<string>>
        {
        }

        [Serializable]
        public class PropertyCallbackHandler : Property<PlatformCallbackHandler>
        {
        }

        internal interface PropertyOverrideControl
        {
            bool HasValue(Platform platform);
            void Clear(Platform platform);
        }

        // This class provides access to a specific property on any Platform object; the property to
        // operate on is determined by the Getter function. This allows client code to operate on
        // platform properties in a generic manner.
        internal struct PropertyAccessor<T> : PropertyOverrideControl
        {
            private readonly Func<PropertyStorage, Property<T>> Getter;
            private readonly T DefaultValue;

            public PropertyAccessor(Func<PropertyStorage, Property<T>> getter, T defaultValue)
            {
                Getter = getter;
                DefaultValue = defaultValue;
            }

            // Determine whether the property has a value in the given platform, or is inherited
            // from the parent.
            public bool HasValue(Platform platform)
            {
                return platform.Active && Getter(platform.Properties).HasValue;
            }

            // Get the (possibly inherited) value of the property for the given platform.
            public T Get(Platform platform)
            {
                for (Platform current = platform; current != null; current = current.Parent)
                {
                    if (current.Active)
                    {
                        Property<T> property = Getter(current.Properties);

                        if (property.HasValue)
                        {
                            return property.Value;
                        }
                    }
                }

#if UNITY_EDITOR
                if (platform is PlatformPlayInEditor)
                {
                    return Get(Settings.EditorSettings.CurrentEditorPlatform);
                }
#endif

                return DefaultValue;
            }

            // Set the value of the property in the given platform, so it is not inherited from the
            // platform's parent.
            public void Set(Platform platform, T value)
            {
                Property<T> property = Getter(platform.Properties);

                property.Value = value;
                property.HasValue = true;
            }

            // Clear the value of the property in the given platform, so it is inherited from the
            // platform's parent.
            public void Clear(Platform platform)
            {
                Getter(platform.Properties).HasValue = false;
            }
        }

        // This class stores all of the inheritable properties for a platform.
        [Serializable]
        public class PropertyStorage
        {
            public PropertyBool LiveUpdate = new PropertyBool();
            public PropertyInt LiveUpdatePort = new PropertyInt();
            public PropertyBool Overlay = new PropertyBool();
            public PropertyScreenPosition OverlayPosition = new PropertyScreenPosition();
            public PropertyInt OverlayFontSize = new PropertyInt();
            public PropertyBool Logging = new PropertyBool();
            public PropertyInt SampleRate = new PropertyInt();
            public PropertyString BuildDirectory = new PropertyString();
            public PropertySpeakerMode SpeakerMode = new PropertySpeakerMode();
            public PropertyInt VirtualChannelCount = new PropertyInt();
            public PropertyInt RealChannelCount = new PropertyInt();
            public PropertyInt DSPBufferLength = new PropertyInt();
            public PropertyInt DSPBufferCount = new PropertyInt();
            public PropertyStringList Plugins = new PropertyStringList();
            public PropertyStringList StaticPlugins = new PropertyStringList();
            public PropertyCallbackHandler CallbackHandler = new PropertyCallbackHandler();
        }

        // Whether this platform is active in the settings UI.
        internal bool Active { get { return active; } }

        // Whether this platform has any properties that are not inherited from the parent.
        internal bool HasAnyOverriddenProperties
        {
            get
            {
                return active &&
                    (
                        Properties.LiveUpdate.HasValue
                        || Properties.LiveUpdatePort.HasValue
                        || Properties.Overlay.HasValue
                        || Properties.OverlayPosition.HasValue
                        || Properties.OverlayFontSize.HasValue
                        || Properties.Logging.HasValue
                        || Properties.SampleRate.HasValue
                        || Properties.BuildDirectory.HasValue
                        || Properties.SpeakerMode.HasValue
                        || Properties.VirtualChannelCount.HasValue
                        || Properties.RealChannelCount.HasValue
                        || Properties.DSPBufferLength.HasValue
                        || Properties.DSPBufferCount.HasValue
                        || Properties.Plugins.HasValue
                        || Properties.StaticPlugins.HasValue
                    );
            }
        }

        // These accessors provide (possibly inherited) property values.
        public TriStateBool LiveUpdate { get { return PropertyAccessors.LiveUpdate.Get(this); } }
        public int LiveUpdatePort { get { return PropertyAccessors.LiveUpdatePort.Get(this); } }
        public TriStateBool Overlay { get { return PropertyAccessors.Overlay.Get(this); } }
        public ScreenPosition OverlayRect { get { return PropertyAccessors.OverlayPosition.Get(this); } }
        public int OverlayFontSize { get { return PropertyAccessors.OverlayFontSize.Get(this); } }
        public void SetOverlayFontSize(int size) { PropertyAccessors.OverlayFontSize.Set(this, size); }
        public TriStateBool Logging { get { return PropertyAccessors.Logging.Get(this); } }
        public int SampleRate { get { return PropertyAccessors.SampleRate.Get(this); } }
        public string BuildDirectory { get { return PropertyAccessors.BuildDirectory.Get(this); } }
        public FMOD.SPEAKERMODE SpeakerMode { get { return PropertyAccessors.SpeakerMode.Get(this); } }
        public int VirtualChannelCount { get { return PropertyAccessors.VirtualChannelCount.Get(this); } }
        public int RealChannelCount { get { return PropertyAccessors.RealChannelCount.Get(this); } }
        public int DSPBufferLength { get { return PropertyAccessors.DSPBufferLength.Get(this); } }
        public int DSPBufferCount { get { return PropertyAccessors.DSPBufferCount.Get(this); } }
        public List<string> Plugins { get { return PropertyAccessors.Plugins.Get(this); } }
        public List<string> StaticPlugins { get { return PropertyAccessors.StaticPlugins.Get(this); } }
        public PlatformCallbackHandler CallbackHandler { get { return PropertyAccessors.CallbackHandler.Get(this); } }

        // These accessors provide full access to properties.
        internal static class PropertyAccessors
        {
            public static readonly PropertyAccessor<TriStateBool> LiveUpdate
                    = new PropertyAccessor<TriStateBool>(properties => properties.LiveUpdate, TriStateBool.Disabled);

            public static readonly PropertyAccessor<int> LiveUpdatePort
                    = new PropertyAccessor<int>(properties => properties.LiveUpdatePort, 9264);

            public static readonly PropertyAccessor<TriStateBool> Overlay
                    = new PropertyAccessor<TriStateBool>(properties => properties.Overlay, TriStateBool.Disabled);

            public static readonly PropertyAccessor<ScreenPosition> OverlayPosition = new PropertyAccessor<ScreenPosition>(properties => properties.OverlayPosition, ScreenPosition.TopLeft);

            public static readonly PropertyAccessor<int> OverlayFontSize = new PropertyAccessor<int>(properties => properties.OverlayFontSize, 14);

            public static readonly PropertyAccessor<TriStateBool> Logging
                    = new PropertyAccessor<TriStateBool>(properties => properties.Logging, TriStateBool.Disabled);

            public static readonly PropertyAccessor<int> SampleRate
                    = new PropertyAccessor<int>(properties => properties.SampleRate, 0);

            public static readonly PropertyAccessor<string> BuildDirectory
                    = new PropertyAccessor<string>(properties => properties.BuildDirectory, "Desktop");

            public static readonly PropertyAccessor<FMOD.SPEAKERMODE> SpeakerMode
                    = new PropertyAccessor<FMOD.SPEAKERMODE>(properties => properties.SpeakerMode, FMOD.SPEAKERMODE.STEREO);

            public static readonly PropertyAccessor<int> VirtualChannelCount
                    = new PropertyAccessor<int>(properties => properties.VirtualChannelCount, 128);

            public static readonly PropertyAccessor<int> RealChannelCount
                    = new PropertyAccessor<int>(properties => properties.RealChannelCount, 32);

            public static readonly PropertyAccessor<int> DSPBufferLength
                    = new PropertyAccessor<int>(properties => properties.DSPBufferLength, 0);

            public static readonly PropertyAccessor<int> DSPBufferCount
                    = new PropertyAccessor<int>(properties => properties.DSPBufferCount, 0);

            public static readonly PropertyAccessor<List<string>> Plugins
                    = new PropertyAccessor<List<string>>(properties => properties.Plugins, null);

            public static readonly PropertyAccessor<List<string>> StaticPlugins
                    = new PropertyAccessor<List<string>>(properties => properties.StaticPlugins, null);

            public static readonly PropertyAccessor<PlatformCallbackHandler> CallbackHandler
                    = new PropertyAccessor<PlatformCallbackHandler>(properties => properties.CallbackHandler, null);
        }

#if UNITY_EDITOR
        // The parent platform from which this platform inherits its property values.
        internal Platform Parent
        {
            get
            {
                return (ParentIdentifier != null) ? Settings.Instance.FindPlatform(ParentIdentifier) : null;
            }
        }

        // The platforms which inherit their property values from this platform.
        internal List<string> ChildIdentifiers { get { return childIdentifiers; } }
#endif

        // Checks whether this platform inherits from the given platform, so we can avoid creating
        // inheritance loops.
        internal bool InheritsFrom(Platform platform)
        {
            if (platform == this)
            {
                return true;
            }
            else if (Parent != null)
            {
                return Parent.InheritsFrom(platform);
            }
            else
            {
                return false;
            }
        }

        internal FMOD.OUTPUTTYPE GetOutputType()
        {
            if (Enum.IsDefined(typeof(FMOD.OUTPUTTYPE), OutputTypeName))
            {
                return (FMOD.OUTPUTTYPE)Enum.Parse(typeof(FMOD.OUTPUTTYPE), OutputTypeName);
            }
            return FMOD.OUTPUTTYPE.AUTODETECT;
        }

#if UNITY_EDITOR
        public struct OutputType
        {
            public string displayName;
            public FMOD.OUTPUTTYPE outputType;
        }

        internal abstract OutputType[] ValidOutputTypes { get; }

        internal virtual int CoreCount { get { return 0; } }
#endif

        internal virtual List<ThreadAffinityGroup> DefaultThreadAffinities { get { return StaticThreadAffinities; } }

        [Serializable]
        public class PropertyThreadAffinityList : Property<List<ThreadAffinityGroup>>
        {
        }

        public IEnumerable<ThreadAffinityGroup> ThreadAffinities
        {
            get
            {
                if (threadAffinities.HasValue)
                {
                    return threadAffinities.Value;
                }
                else
                {
                    return DefaultThreadAffinities;
                }
            }
        }

        internal PropertyThreadAffinityList ThreadAffinitiesProperty { get { return threadAffinities; } }

        internal virtual List<CodecChannelCount> DefaultCodecChannels { get { return staticCodecChannels; } }

        private static List<CodecChannelCount> staticCodecChannels = new List<CodecChannelCount>()
        {
            new CodecChannelCount { format = CodecType.FADPCM, channels = 32 },
            new CodecChannelCount { format = CodecType.Vorbis, channels = 0 },
        };

        [Serializable]
        internal class PropertyCodecChannels : Property<List<CodecChannelCount>>
        {
        }

        [SerializeField]
        private PropertyCodecChannels codecChannels = new PropertyCodecChannels();

        internal List<CodecChannelCount> CodecChannels
        {
            get
            {
                if (codecChannels.HasValue)
                {
                    return codecChannels.Value;
                }
                else
                {
                    return DefaultCodecChannels;
                }
            }
        }

        internal PropertyCodecChannels CodecChannelsProperty { get { return codecChannels; } }
    }
}
