<commit_before>#region LGPL License
/*
Axiom Game Engine Library
Copyright (C) 2003  Axiom Project Team

The overall design, and a majority of the core engine and rendering code 
contained within this library is a derivative of the open source Object Oriented 
Graphics Engine OGRE, which can be found at http://ogre.sourceforge.net.  
Many thanks to the OGRE team for maintaining such a high quality project.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#endregion

using System;
using System.Collections;
using System.Collections.Specialized;
using System.IO;
using System.Reflection;
using Axiom.Controllers;
using Axiom.Core;
using Axiom.FileSystem;
using Axiom.MathLib;
using Axiom.Scripting;

namespace Axiom.Graphics {
    /// <summary>
    /// Summary description for MaterialManager.
    /// </summary>
    public class MaterialManager : ResourceManager {
        #region Singleton implementation

        static MaterialManager() { Init(); }
        protected MaterialManager() {}
        protected static MaterialManager instance;

        public static MaterialManager Instance {
            get { return instance; }
        }

        public static void Init() {
            instance = new MaterialManager();

            instance.Initialize();

            // just create the default BaseWhite material
            Material baseWhite = (Material)instance.Create("BaseWhite");
            baseWhite.CreateTechnique().CreatePass();
            baseWhite.Lighting = false;

            instance.defaultTextureFiltering = TextureFiltering.Bilinear;
            instance.defaultAnisotropy = 1;
        }
		
        #endregion

        #region Delegates

        delegate void PassAttributeParser(string[] values, Pass pass);
        delegate void TextureUnitAttributeParser(string[] values, TextureUnitState texUnit);

        #endregion

        #region Member variables

        /// <summary>Lookup table of methods that can be used to parse material attributes.</summary>
        protected Hashtable passAttribParsers = new Hashtable();
        protected Hashtable texUnitAttribParsers = new Hashtable();

        protected TextureFiltering defaultTextureFiltering;
        protected int defaultAnisotropy;
		
        // constants for material section types
        const string GpuProgram = "GpuProgram";
        const string GpuProgramDef = "GpuProgramDef";
        const string TextureUnit = "TextureUnit";
        const string Pass = "Pass";

        #endregion

        #region Properties

        /// <summary>
        ///    Sets the default anisotropy level to be used for loaded textures, for when textures are
        ///    loaded automatically (e.g. by Material class) or when 'Load' is called with the default
        ///    parameters by the application.
        /// </summary>
        public int DefaultAnisotropy {
            get {
                return defaultAnisotropy;
            }
            set {
                defaultAnisotropy = value;

                // TODO: Fix me dammit, need aniso on material
                // reset for all current textures
                foreach(Material material in resourceList.Values) {
                    material.TextureFiltering = defaultTextureFiltering;
                }
            }
        }

        /// <summary>
        ///    Sets the default texture filtering to use for all textures in the engine.
        /// </summary>
        public TextureFiltering DefaultTextureFiltering {
            get {
                return defaultTextureFiltering;
            }
            set {
                defaultTextureFiltering = value;

                // reset for all current textures
                foreach(Material material in resourceList.Values) {
                    material.TextureFiltering = defaultTextureFiltering;
                }
            }
        }

        #endregion Properties

        /// <summary>
        /// 
        /// </summary>
        public void Initialize() {
            // register all attribute parsers
            RegisterParsers();
        }

        /// <summary>
        ///		Registers all attribute names with their respective parser.
        /// </summary>
        /// <remarks>
        ///		Methods meant to serve as attribute parsers should use a method attribute to 
        /// </remarks>
        protected void RegisterParsers() {
            MethodInfo[] methods = this.GetType().GetMethods();
			
            // loop through all methods and look for ones marked with attributes
            for(int i = 0; i < methods.Length; i++) {
                // get the current method in the loop
                MethodInfo method = methods[i];
				
                // see if the method should be used to parse one or more material attributes
                AttributeParserAttribute[] parserAtts = 
                    (AttributeParserAttribute[])method.GetCustomAttributes(typeof(AttributeParserAttribute), true);

                // loop through each one we found and register its parser
                for(int j = 0; j < parserAtts.Length; j++) {
                    AttributeParserAttribute parserAtt = parserAtts[j];

                    switch(parserAtt.ParserType) {
                            // this method should parse a material attribute
                        case Pass:
                            passAttribParsers.Add(parserAtt.Name, Delegate.CreateDelegate(typeof(PassAttributeParser), method));
                            break;

                            // this method should parse a texture layer attribute
                        case TextureUnit:
                            texUnitAttribParsers.Add(parserAtt.Name, Delegate.CreateDelegate(typeof(TextureUnitAttributeParser), method));
                            break;
                    } // switch
                } // for
            } // for
        }


        #region Implementation of ResourceManager

        public new Material GetByName(string name) {
            return (Material)base.GetByName(name);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override Resource Create(string name) {
            if(resourceList[name] != null)
                throw new Axiom.Exceptions.AxiomException(string.Format("Cananot create a duplicate material named '{0}'.", name));

            // create a material
            Material material = new Material(name);

            resourceList[name] = material;
				
            return material;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        /// <param name="priority"></param>
        /// <returns></returns>
        public Material Load(string name, int priority) {
            Material material = null;

            // if the resource isn't cached, create it
            if(!resourceList.ContainsKey(name)) {
                material = (Material)Create(name);
                base.Load(material, priority);
            }
            else {
                // get the cached version
                material = (Material)resourceList[name];
            }

            return material;
        }

        /// <summary>
        ///		Look for material scripts in all known sources and parse them.
        /// </summary>
        /// <param name="extension"></param>
        public void ParseAllSources() {
            string extension = ".material";

            // parse gpu programs first
            GpuProgramManager.Instance.ParseAllSources();

            // search archives
            for(int i = 0; i < archives.Count; i++) {
                Archive archive = (Archive)archives[i];
                string[] files = archive.GetFileNamesLike("", extension);

                for(int j = 0; j < files.Length; j++) {
                    Stream data = archive.ReadFile(files[j]);

                    // parse the materials
                    ParseScript(data);
                }
            }

            // search common archives
            for(int i = 0; i < commonArchives.Count; i++) {
                Archive archive = (Archive)commonArchives[i];
                string[] files = archive.GetFileNamesLike("", extension);

                for(int j = 0; j < files.Length; j++) {
                    Stream data = archive.ReadFile(files[j]);

                    // parse the materials
                    ParseScript(data);
                }
            }
        }

        #endregion

        /// <summary>
        ///		
        /// </summary>
        protected void ParseScript(Stream stream) {
            StreamReader script = new StreamReader(stream, System.Text.Encoding.ASCII);

            string line = "";

            // parse through the data to the end
            while((line = ParseHelper.ReadLine(script)) != null) {
                // ignore blank lines and comments
                if(line.Length == 0 || line.StartsWith("//")) {
                    continue;
                }

                if(line.StartsWith("material")) {
                    string[] parms = line.Split(new char[] {' '}, 2);

                    if(parms.Length != 2) {
                        ParseHelper.LogParserError("material", "Top level", "Materials must have a name.");
                        // skip this one
                        ParseHelper.SkipToNextCloseBrace(script);
                        continue;
                    }
                    
                    ParseHelper.SkipToNextOpenBrace(script);

                    ParseMaterial(script, parms[1]);
                }
            }
        }


        protected void ParseMaterial(TextReader script, string name) {
            // create a new material
            Material material = (Material)Create(name);

            string line = "";

            while((line = ParseHelper.ReadLine(script)) != null) {
                // ignore blank lines and comments
                if(line.Length == 0 || line.StartsWith("//")) {
                    continue;
                }

                if(line == "}") {
                    // compile the material
                    material.Compile();

                    return;
                }

                if(line == "technique") {
                    ParseHelper.SkipToNextOpenBrace(script);
                    ParseTechnique(script, material);
                }
                else {
                    ParseHelper.LogParserError("material", "material", "Only techniques can be child blocks for a material block.");
                }
            }
        }

        protected void ParseTechnique(TextReader script, Material material) {
            // create a new technique
            Technique technique = material.CreateTechnique();

            string line = "";

            while((line = ParseHelper.ReadLine(script)) != null) {
                // ignore blank lines and comments
                if(line.Length == 0 || line.StartsWith("//")) {
                    continue;
                }

                if(line == "}") {
                    return;
                }

                if(line == "pass") {
                    ParseHelper.SkipToNextOpenBrace(script);
                    ParsePass(script, technique);
                }
                else {
                    ParseHelper.LogParserError("technique", "technique", "Only passes can be child blocks for a technique block.");
                }
            }
        }

        /// <summary>
        ///		Parses a texture layer in a material script.
        /// </summary>
        /// <param name="script"></param>
        /// <param name="material"></param>
        protected void ParseTextureUnit(TextReader script, Pass pass) {
            TextureUnitState texUnit = pass.CreateTextureUnitState("");

            string line = "";

            while((line = ParseHelper.ReadLine(script)) != null) {
                // have we reached the end of the layer
                if(line == "}")
                    return;
                else {
                    // split attribute line by spaces
                    string[] values = line.Split(' ');

                    // make sure this attribute exists
                    if(!texUnitAttribParsers.ContainsKey(values[0]))
                        System.Diagnostics.Trace.WriteLine(string.Format("Unknown layer attribute: {0}", values[0]));
                    else {
                        TextureUnitAttributeParser parser = (TextureUnitAttributeParser)texUnitAttribParsers[values[0]];

                        if(values[0] != "texture" && values[0] != "cubic_texture" && 	values[0] != "anim_texture") {
                            // lowercase all params if not a texture attrib of any sort, since texture filenames
                            // can be case sensitive
                            for(int i = 0; i < values.Length; i++)
                                values[0] = values[0].ToLower();
                        }

                        parser(ParseHelper.GetParams(values), texUnit);
                    }
                }
            }
        }

        /// <summary>
        ///		Parses an attribute line for the current pass.
        /// </summary>
        /// <param name="line"></param>
        /// <param name="technique"></param>
        protected void ParsePass(TextReader script, Technique technique) {

            Pass pass = technique.CreatePass();

            string line = "";

            while((line = ParseHelper.ReadLine(script)) != null) {
                if(line.Length == 0 || line.StartsWith("//")) {
                    continue;
                }

                if(line == "}") {
                    return;
                }

                if(line == "texture_unit") {
                    ParseHelper.SkipToNextOpenBrace(script);
                    ParseTextureUnit(script, pass);
                }
                else if(line.StartsWith("vertex_program_ref")) {
                    string[] parms = line.Split(' ');

                    if(parms.Length != 2) {
                        ParseHelper.LogParserError("vertex_program_ref", pass.Parent.Parent.Name, "A name must be specified for a vertex program reference.");
                        ParseHelper.SkipToNextCloseBrace(script);
                        return;
                    }

                    ParseHelper.SkipToNextOpenBrace(script);
                    ParseGpuProgramRef(script, parms[1], pass, GpuProgramType.Vertex);
                }
                else if(line.StartsWith("fragment_program_ref")) {
                    string[] parms = line.Split(' ');

                    if(parms.Length != 2) {
                        ParseHelper.LogParserError("fragment_program_ref", pass.Parent.Parent.Name, "A name must be specified for a fragment program reference.");
                        ParseHelper.SkipToNextCloseBrace(script);
                        return;
                    }
                    
                    ParseHelper.SkipToNextOpenBrace(script);
                    ParseGpuProgramRef(script, parms[1], pass, GpuProgramType.Fragment);
                }
                else {
                    // split attribute line by spaces
                    string[] values = line.Split(' ');

                    // make sure this attribute exists
                    if(!passAttribParsers.ContainsKey(values[0]))
                        System.Diagnostics.Trace.WriteLine(string.Format("Unknown pass attribute: {0}", values[0]));
                    else {
                        PassAttributeParser parser = (PassAttributeParser)passAttribParsers[values[0]];
                        parser(ParseHelper.GetParams(values), pass);
                    }
                }
            }
        }

        protected void ParseGpuProgramRef(TextReader script, string name, Pass pass, GpuProgramType type) {
            string line = "";
            GpuProgramParameters programParams = null;

            switch(type) {
                case GpuProgramType.Vertex:
                    pass.VertexProgramName = name;
                    programParams = pass.VertexProgramParameters;
                    break;

                case GpuProgramType.Fragment:
                    pass.FragmentProgramName = name;
                    programParams = pass.FragmentProgramParameters;
                    break;
            }

            while((line = ParseHelper.ReadLine(script)) != null) {
                if(line.Length == 0 || line.StartsWith("//")) {
                    continue;
                }

                if(line == "}") {
                    return;
                }

                string[] parms = line.Split(' ');
                int index = 0;

                switch(parms[0]) {
                    case "param_indexed":
                        index = int.Parse(parms[1]);
                        string dataType = parms[2];

                        if(dataType == "float4") {
                            if(parms.Length != 7) {
                                ParseHelper.LogParserError("param_indexed", pass.Parent.Parent.Name, "Float4 gpu program params must have 4 components specified.");
                                ParseHelper.SkipToNextCloseBrace(script);
                                return;
                            }

                            Vector4 vec = new Vector4(float.Parse(parms[3]), float.Parse(parms[4]), float.Parse(parms[5]), float.Parse(parms[6]));
                            programParams.SetConstant(index, vec);
                        }
                        // TODO: more types
                        break;

                    case "param_indexed_auto":
                        index = int.Parse(parms[1]);
                        string constant = parms[2];
                        int extraInfo = (parms.Length == 4) ? int.Parse(parms[3]) : 0;

                        object val = ScriptEnumAttribute.Lookup(constant, typeof(AutoConstants));

                        if(val != null) {
                            AutoConstants autoConstant = (AutoConstants)val;
                            programParams.SetAutoConstant(index, autoConstant, extraInfo);
                        }
                        else {
                            ParseHelper.LogParserError("vertex_program_ref", pass.Parent.Parent.Name, "Unrecognized auto contant type.");
                        }

                        break;

                    case "param_named":
                        name = parms[1];
                        dataType = parms[2];

                        if(dataType == "float4") {
                            if(parms.Length != 7) {
                                ParseHelper.LogParserError("param_named", pass.Parent.Parent.Name, "Float4 gpu program params must have 4 components specified.");
                                ParseHelper.SkipToNextCloseBrace(script);
                                return;
                            }

                            Vector4 vec = new Vector4(float.Parse(parms[3]), float.Parse(parms[4]), float.Parse(parms[5]), float.Parse(parms[6]));
                            programParams.SetNamedConstant(name, vec);
                        }
                        // TODO: more types
                        break;

                    case "param_named_auto":

                        string paramName = parms[1];
                        constant = parms[2];
                        extraInfo = (parms.Length == 4) ? int.Parse(parms[3]) : 0;

                        val = ScriptEnumAttribute.Lookup(constant, typeof(AutoConstants));

                        if(val != null) {
                            AutoConstants autoConstant = (AutoConstants)val;
                            programParams.SetNamedAutoConstant(paramName, autoConstant, extraInfo);
                        }
                        else {
                            ParseHelper.LogParserError("vertex_program_ref", pass.Parent.Parent.Name, "Unrecognized auto contant type.");
                        }

                        break;

                    default:
                        ParseHelper.LogParserError("vertex_program_ref", pass.Parent.Parent.Name, "Unknown vertex program ref param");
                        break;
                }
            }
        }

        #region Material attribute parser methods

        [AttributeParser("ambient", Pass)]
        public static void ParseAmbient(string[] values, Pass pass) {
            if(values.Length != 3 && values.Length != 4) {
                ParseHelper.LogParserError("ambient", pass.Parent.Name, "Expected 3-4 params");
                return;
            }
			
            pass.Ambient = ParseHelper.ParseColor(values);
        }

        [AttributeParser("depth_write", Pass)]
        public static void ParseDepthWrite(string[] values, Pass pass) {
            if(values.Length != 1) {
                ParseHelper.LogParserError("depth_write", pass.Parent.Name, "Expected value 'on' or 'off'");
                return;
            }

            switch(values[0]) {
                case "on":
                    pass.DepthWrite = true;
                    break;
                case "off":
                    pass.DepthWrite = false;
                    break;
                default:
                    ParseHelper.LogParserError("depth_write", pass.Parent.Name, "Invalid depth write value, must be 'on' or 'off'");
                    return;
            }
        }

        [AttributeParser("diffuse", Pass)]
        public static void ParseDiffuse(string[] values, Pass pass) {
            if(values.Length != 3 && values.Length != 4) {
                ParseHelper.LogParserError("diffuse", pass.Parent.Name, "Expected 3-4 params");
                return;
            }

            pass.Diffuse = ParseHelper.ParseColor(values);
        }

        [AttributeParser("depth_check", Pass)]
        public static void ParseDepthCheck(string[] values, Pass pass) {
            if(values.Length != 1) {
                ParseHelper.LogParserError("depth_check", pass.Parent.Name, "Expected param 'on' or 'off'");
                return;
            }

            switch(values[0]) {
                case "on":
                    pass.DepthCheck = true;
                    break;
                case "off":
                    pass.DepthCheck = false;
                    break;
                default:
                    ParseHelper.LogParserError("depth_check", pass.Parent.Name, "Invalid depth_check value, must be 'on' or 'off'");
                    return;
            }
        }

        [AttributeParser("lighting", Pass)]
        public static void ParseLighting(string[] values, Pass pass) {
            if(values.Length != 1) {
                ParseHelper.LogParserError("lighting", pass.Parent.Name, "Expected param 'on' or 'off'");
                return;
            }

            switch(values[0]) {
                case "on":
                    pass.LightingEnabled = true;
                    break;
                case "off":
                    pass.LightingEnabled = false;
                    break;
                default:
                    ParseHelper.LogParserError("lighting", pass.Parent.Name, "Invalid lighting value, must be 'on' or 'off'");
                    return;
            }
        }

        [AttributeParser("scene_blend", Pass)]
        public static void ParseSceneBlend(string[] values, Pass pass) {
            if(values.Length != 1) {
                ParseHelper.LogParserError("scene_blend", pass.Parent.Name, "Expected 1 param.");
                return;
            }

            // lookup the real enum equivalent to the script value
            object val = ScriptEnumAttribute.Lookup(values[0], typeof(SceneBlendType));

            // if a value was found, assign it
            if(val != null)
                pass.SetSceneBlending((SceneBlendType)val);
            else
                ParseHelper.LogParserError("scene_blend", pass.Parent.Name, "Invalid enum value");
        }

        [AttributeParser("cull_hardware", Pass)]
        public static void ParseCullHardware(string[] values, Pass pass) {
            if(values.Length != 1) {
                ParseHelper.LogParserError("cull_hardware", pass.Parent.Name, "Expected 2 params.");
                return;
            }

            // lookup the real enum equivalent to the script value
            object val = ScriptEnumAttribute.Lookup(values[0], typeof(CullingMode));

            // if a value was found, assign it
            if(val != null)
                pass.CullMode = (CullingMode)val;
            else
                ParseHelper.LogParserError("cull_hardware", pass.Parent.Name, "Invalid enum value");
        }

        [AttributeParser("cull_software", Pass)]
        public static void ParseCullSoftware(string[] values, Pass pass) {
            if(values.Length != 1) {
                ParseHelper.LogParserError("cull_software", pass.Parent.Name, "Invalid enum value");
                return;
            }

            // lookup the real enum equivalent to the script value
            object val = ScriptEnumAttribute.Lookup(values[0], typeof(ManualCullingMode));
<commit_msg>Fix from "Guest" (heh, better late than never) to allow both single and double param modes for scene_blend.<commit_after>#region LGPL License
/*
Axiom Game Engine Library
Copyright (C) 2003  Axiom Project Team

The overall design, and a majority of the core engine and rendering code 
contained within this library is a derivative of the open source Object Oriented 
Graphics Engine OGRE, which can be found at http://ogre.sourceforge.net.  
Many thanks to the OGRE team for maintaining such a high quality project.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#endregion

using System;
using System.Collections;
using System.Collections.Specialized;
using System.IO;
using System.Reflection;
using Axiom.Controllers;
using Axiom.Core;
using Axiom.FileSystem;
using Axiom.MathLib;
using Axiom.Scripting;

namespace Axiom.Graphics {
    /// <summary>
    /// Summary description for MaterialManager.
    /// </summary>
    public class MaterialManager : ResourceManager {
        #region Singleton implementation

        static MaterialManager() { Init(); }
        protected MaterialManager() {}
        protected static MaterialManager instance;

        public static MaterialManager Instance {
            get { return instance; }
        }

        public static void Init() {
            instance = new MaterialManager();

            instance.Initialize();

            // just create the default BaseWhite material
            Material baseWhite = (Material)instance.Create("BaseWhite");
            baseWhite.CreateTechnique().CreatePass();
            baseWhite.Lighting = false;

            instance.defaultTextureFiltering = TextureFiltering.Bilinear;
            instance.defaultAnisotropy = 1;
        }
		
        #endregion

        #region Delegates

        delegate void PassAttributeParser(string[] values, Pass pass);
        delegate void TextureUnitAttributeParser(string[] values, TextureUnitState texUnit);

        #endregion

        #region Member variables

        /// <summary>Lookup table of methods that can be used to parse material attributes.</summary>
        protected Hashtable passAttribParsers = new Hashtable();
        protected Hashtable texUnitAttribParsers = new Hashtable();

        protected TextureFiltering defaultTextureFiltering;
        protected int defaultAnisotropy;
		
        // constants for material section types
        const string GpuProgram = "GpuProgram";
        const string GpuProgramDef = "GpuProgramDef";
        const string TextureUnit = "TextureUnit";
        const string Pass = "Pass";

        #endregion

        #region Properties

        /// <summary>
        ///    Sets the default anisotropy level to be used for loaded textures, for when textures are
        ///    loaded automatically (e.g. by Material class) or when 'Load' is called with the default
        ///    parameters by the application.
        /// </summary>
        public int DefaultAnisotropy {
            get {
                return defaultAnisotropy;
            }
            set {
                defaultAnisotropy = value;

                // TODO: Fix me dammit, need aniso on material
                // reset for all current textures
                foreach(Material material in resourceList.Values) {
                    material.TextureFiltering = defaultTextureFiltering;
                }
            }
        }

        /// <summary>
        ///    Sets the default texture filtering to use for all textures in the engine.
        /// </summary>
        public TextureFiltering DefaultTextureFiltering {
            get {
                return defaultTextureFiltering;
            }
            set {
                defaultTextureFiltering = value;

                // reset for all current textures
                foreach(Material material in resourceList.Values) {
                    material.TextureFiltering = defaultTextureFiltering;
                }
            }
        }

        #endregion Properties

        /// <summary>
        /// 
        /// </summary>
        public void Initialize() {
            // register all attribute parsers
            RegisterParsers();
        }

        /// <summary>
        ///		Registers all attribute names with their respective parser.
        /// </summary>
        /// <remarks>
        ///		Methods meant to serve as attribute parsers should use a method attribute to 
        /// </remarks>
        protected void RegisterParsers() {
            MethodInfo[] methods = this.GetType().GetMethods();
			
            // loop through all methods and look for ones marked with attributes
            for(int i = 0; i < methods.Length; i++) {
                // get the current method in the loop
                MethodInfo method = methods[i];
				
                // see if the method should be used to parse one or more material attributes
                AttributeParserAttribute[] parserAtts = 
                    (AttributeParserAttribute[])method.GetCustomAttributes(typeof(AttributeParserAttribute), true);

                // loop through each one we found and register its parser
                for(int j = 0; j < parserAtts.Length; j++) {
                    AttributeParserAttribute parserAtt = parserAtts[j];

                    switch(parserAtt.ParserType) {
                            // this method should parse a material attribute
                        case Pass:
                            passAttribParsers.Add(parserAtt.Name, Delegate.CreateDelegate(typeof(PassAttributeParser), method));
                            break;

                            // this method should parse a texture layer attribute
                        case TextureUnit:
                            texUnitAttribParsers.Add(parserAtt.Name, Delegate.CreateDelegate(typeof(TextureUnitAttributeParser), method));
                            break;
                    } // switch
                } // for
            } // for
        }


        #region Implementation of ResourceManager

        public new Material GetByName(string name) {
            return (Material)base.GetByName(name);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override Resource Create(string name) {
            if(resourceList[name] != null)
                throw new Axiom.Exceptions.AxiomException(string.Format("Cananot create a duplicate material named '{0}'.", name));

            // create a material
            Material material = new Material(name);

            resourceList[name] = material;
				
            return material;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        /// <param name="priority"></param>
        /// <returns></returns>
        public Material Load(string name, int priority) {
            Material material = null;

            // if the resource isn't cached, create it
            if(!resourceList.ContainsKey(name)) {
                material = (Material)Create(name);
                base.Load(material, priority);
            }
            else {
                // get the cached version
                material = (Material)resourceList[name];
            }

            return material;
        }

        /// <summary>
        ///		Look for material scripts in all known sources and parse them.
        /// </summary>
        /// <param name="extension"></param>
        public void ParseAllSources() {
            string extension = ".material";

            // parse gpu programs first
            GpuProgramManager.Instance.ParseAllSources();

            // search archives
            for(int i = 0; i < archives.Count; i++) {
                Archive archive = (Archive)archives[i];
                string[] files = archive.GetFileNamesLike("", extension);

                for(int j = 0; j < files.Length; j++) {
                    Stream data = archive.ReadFile(files[j]);

                    // parse the materials
                    ParseScript(data);
                }
            }

            // search common archives
            for(int i = 0; i < commonArchives.Count; i++) {
                Archive archive = (Archive)commonArchives[i];
                string[] files = archive.GetFileNamesLike("", extension);

                for(int j = 0; j < files.Length; j++) {
                    Stream data = archive.ReadFile(files[j]);

                    // parse the materials
                    ParseScript(data);
                }
            }
        }

        #endregion

        /// <summary>
        ///		
        /// </summary>
        protected void ParseScript(Stream stream) {
            StreamReader script = new StreamReader(stream, System.Text.Encoding.ASCII);

            string line = "";

            // parse through the data to the end
            while((line = ParseHelper.ReadLine(script)) != null) {
                // ignore blank lines and comments
                if(line.Length == 0 || line.StartsWith("//")) {
                    continue;
                }

                if(line.StartsWith("material")) {
                    string[] parms = line.Split(new char[] {' '}, 2);

                    if(parms.Length != 2) {
                        ParseHelper.LogParserError("material", "Top level", "Materials must have a name.");
                        // skip this one
                        ParseHelper.SkipToNextCloseBrace(script);
                        continue;
                    }
                    
                    ParseHelper.SkipToNextOpenBrace(script);

                    ParseMaterial(script, parms[1]);
                }
            }
        }


        protected void ParseMaterial(TextReader script, string name) {
            // create a new material
            Material material = (Material)Create(name);

            string line = "";

            while((line = ParseHelper.ReadLine(script)) != null) {
                // ignore blank lines and comments
                if(line.Length == 0 || line.StartsWith("//")) {
                    continue;
                }

                if(line == "}") {
                    // compile the material
                    material.Compile();

                    return;
                }

                if(line == "technique") {
                    ParseHelper.SkipToNextOpenBrace(script);
                    ParseTechnique(script, material);
                }
                else {
                    ParseHelper.LogParserError("material", "material", "Only techniques can be child blocks for a material block.");
                }
            }
        }

        protected void ParseTechnique(TextReader script, Material material) {
            // create a new technique
            Technique technique = material.CreateTechnique();

            string line = "";

            while((line = ParseHelper.ReadLine(script)) != null) {
                // ignore blank lines and comments
                if(line.Length == 0 || line.StartsWith("//")) {
                    continue;
                }

                if(line == "}") {
                    return;
                }

                if(line == "pass") {
                    ParseHelper.SkipToNextOpenBrace(script);
                    ParsePass(script, technique);
                }
                else {
                    ParseHelper.LogParserError("technique", "technique", "Only passes can be child blocks for a technique block.");
                }
            }
        }

        /// <summary>
        ///		Parses a texture layer in a material script.
        /// </summary>
        /// <param name="script"></param>
        /// <param name="material"></param>
        protected void ParseTextureUnit(TextReader script, Pass pass) {
            TextureUnitState texUnit = pass.CreateTextureUnitState("");

            string line = "";

            while((line = ParseHelper.ReadLine(script)) != null) {
                // have we reached the end of the layer
                if(line == "}")
                    return;
                else {
                    // split attribute line by spaces
                    string[] values = line.Split(' ');

                    // make sure this attribute exists
                    if(!texUnitAttribParsers.ContainsKey(values[0]))
                        System.Diagnostics.Trace.WriteLine(string.Format("Unknown layer attribute: {0}", values[0]));
                    else {
                        TextureUnitAttributeParser parser = (TextureUnitAttributeParser)texUnitAttribParsers[values[0]];

                        if(values[0] != "texture" && values[0] != "cubic_texture" && 	values[0] != "anim_texture") {
                            // lowercase all params if not a texture attrib of any sort, since texture filenames
                            // can be case sensitive
                            for(int i = 0; i < values.Length; i++)
                                values[0] = values[0].ToLower();
                        }

                        parser(ParseHelper.GetParams(values), texUnit);
                    }
                }
            }
        }

        /// <summary>
        ///		Parses an attribute line for the current pass.
        /// </summary>
        /// <param name="line"></param>
        /// <param name="technique"></param>
        protected void ParsePass(TextReader script, Technique technique) {

            Pass pass = technique.CreatePass();

            string line = "";

            while((line = ParseHelper.ReadLine(script)) != null) {
                if(line.Length == 0 || line.StartsWith("//")) {
                    continue;
                }

                if(line == "}") {
                    return;
                }

                if(line == "texture_unit") {
                    ParseHelper.SkipToNextOpenBrace(script);
                    ParseTextureUnit(script, pass);
                }
                else if(line.StartsWith("vertex_program_ref")) {
                    string[] parms = line.Split(' ');

                    if(parms.Length != 2) {
                        ParseHelper.LogParserError("vertex_program_ref", pass.Parent.Parent.Name, "A name must be specified for a vertex program reference.");
                        ParseHelper.SkipToNextCloseBrace(script);
                        return;
                    }

                    ParseHelper.SkipToNextOpenBrace(script);
                    ParseGpuProgramRef(script, parms[1], pass, GpuProgramType.Vertex);
                }
                else if(line.StartsWith("fragment_program_ref")) {
                    string[] parms = line.Split(' ');

                    if(parms.Length != 2) {
                        ParseHelper.LogParserError("fragment_program_ref", pass.Parent.Parent.Name, "A name must be specified for a fragment program reference.");
                        ParseHelper.SkipToNextCloseBrace(script);
                        return;
                    }
                    
                    ParseHelper.SkipToNextOpenBrace(script);
                    ParseGpuProgramRef(script, parms[1], pass, GpuProgramType.Fragment);
                }
                else {
                    // split attribute line by spaces
                    string[] values = line.Split(' ');

                    // make sure this attribute exists
                    if(!passAttribParsers.ContainsKey(values[0]))
                        System.Diagnostics.Trace.WriteLine(string.Format("Unknown pass attribute: {0}", values[0]));
                    else {
                        PassAttributeParser parser = (PassAttributeParser)passAttribParsers[values[0]];
                        parser(ParseHelper.GetParams(values), pass);
                    }
                }
            }
        }

        protected void ParseGpuProgramRef(TextReader script, string name, Pass pass, GpuProgramType type) {
            string line = "";
            GpuProgramParameters programParams = null;

            switch(type) {
                case GpuProgramType.Vertex:
                    pass.VertexProgramName = name;
                    programParams = pass.VertexProgramParameters;
                    break;

                case GpuProgramType.Fragment:
                    pass.FragmentProgramName = name;
                    programParams = pass.FragmentProgramParameters;
                    break;
            }

            while((line = ParseHelper.ReadLine(script)) != null) {
                if(line.Length == 0 || line.StartsWith("//")) {
                    continue;
                }

                if(line == "}") {
                    return;
                }

                string[] parms = line.Split(' ');
                int index = 0;

                switch(parms[0]) {
                    case "param_indexed":
                        index = int.Parse(parms[1]);
                        string dataType = parms[2];

                        if(dataType == "float4") {
                            if(parms.Length != 7) {
                                ParseHelper.LogParserError("param_indexed", pass.Parent.Parent.Name, "Float4 gpu program params must have 4 components specified.");
                                ParseHelper.SkipToNextCloseBrace(script);
                                return;
                            }

                            Vector4 vec = new Vector4(float.Parse(parms[3]), float.Parse(parms[4]), float.Parse(parms[5]), float.Parse(parms[6]));
                            programParams.SetConstant(index, vec);
                        }
                        // TODO: more types
                        break;

                    case "param_indexed_auto":
                        index = int.Parse(parms[1]);
                        string constant = parms[2];
                        int extraInfo = (parms.Length == 4) ? int.Parse(parms[3]) : 0;

                        object val = ScriptEnumAttribute.Lookup(constant, typeof(AutoConstants));

                        if(val != null) {
                            AutoConstants autoConstant = (AutoConstants)val;
                            programParams.SetAutoConstant(index, autoConstant, extraInfo);
                        }
                        else {
                            ParseHelper.LogParserError("vertex_program_ref", pass.Parent.Parent.Name, "Unrecognized auto contant type.");
                        }

                        break;

                    case "param_named":
                        name = parms[1];
                        dataType = parms[2];

                        if(dataType == "float4") {
                            if(parms.Length != 7) {
                                ParseHelper.LogParserError("param_named", pass.Parent.Parent.Name, "Float4 gpu program params must have 4 components specified.");
                                ParseHelper.SkipToNextCloseBrace(script);
                                return;
                            }

                            Vector4 vec = new Vector4(float.Parse(parms[3]), float.Parse(parms[4]), float.Parse(parms[5]), float.Parse(parms[6]));
                            programParams.SetNamedConstant(name, vec);
                        }
                        // TODO: more types
                        break;

                    case "param_named_auto":

                        string paramName = parms[1];
                        constant = parms[2];
                        extraInfo = (parms.Length == 4) ? int.Parse(parms[3]) : 0;

                        val = ScriptEnumAttribute.Lookup(constant, typeof(AutoConstants));

                        if(val != null) {
                            AutoConstants autoConstant = (AutoConstants)val;
                            programParams.SetNamedAutoConstant(paramName, autoConstant, extraInfo);
                        }
                        else {
                            ParseHelper.LogParserError("vertex_program_ref", pass.Parent.Parent.Name, "Unrecognized auto contant type.");
                        }

                        break;

                    default:
                        ParseHelper.LogParserError("vertex_program_ref", pass.Parent.Parent.Name, "Unknown vertex program ref param");
                        break;
                }
            }
        }

        #region Material attribute parser methods

        [AttributeParser("ambient", Pass)]
        public static void ParseAmbient(string[] values, Pass pass) {
            if(values.Length != 3 && values.Length != 4) {
                ParseHelper.LogParserError("ambient", pass.Parent.Name, "Expected 3-4 params");
                return;
            }
			
            pass.Ambient = ParseHelper.ParseColor(values);
        }

        [AttributeParser("depth_write", Pass)]
        public static void ParseDepthWrite(string[] values, Pass pass) {
            if(values.Length != 1) {
                ParseHelper.LogParserError("depth_write", pass.Parent.Name, "Expected value 'on' or 'off'");
                return;
            }

            switch(values[0]) {
                case "on":
                    pass.DepthWrite = true;
                    break;
                case "off":
                    pass.DepthWrite = false;
                    break;
                default:
                    ParseHelper.LogParserError("depth_write", pass.Parent.Name, "Invalid depth write value, must be 'on' or 'off'");
                    return;
            }
        }

        [AttributeParser("diffuse", Pass)]
        public static void ParseDiffuse(string[] values, Pass pass) {
            if(values.Length != 3 && values.Length != 4) {
                ParseHelper.LogParserError("diffuse", pass.Parent.Name, "Expected 3-4 params");
                return;
            }

            pass.Diffuse = ParseHelper.ParseColor(values);
        }

        [AttributeParser("depth_check", Pass)]
        public static void ParseDepthCheck(string[] values, Pass pass) {
            if(values.Length != 1) {
                ParseHelper.LogParserError("depth_check", pass.Parent.Name, "Expected param 'on' or 'off'");
                return;
            }

            switch(values[0]) {
                case "on":
                    pass.DepthCheck = true;
                    break;
                case "off":
                    pass.DepthCheck = false;
                    break;
                default:
                    ParseHelper.LogParserError("depth_check", pass.Parent.Name, "Invalid depth_check value, must be 'on' or 'off'");
                    return;
            }
        }

        [AttributeParser("lighting", Pass)]
        public static void ParseLighting(string[] values, Pass pass) {
            if(values.Length != 1) {
                ParseHelper.LogParserError("lighting", pass.Parent.Name, "Expected param 'on' or 'off'");
                return;
            }

            switch(values[0]) {
                case "on":
                    pass.LightingEnabled = true;
                    break;
                case "off":
                    pass.LightingEnabled = false;
                    break;
                default:
                    ParseHelper.LogParserError("lighting", pass.Parent.Name, "Invalid lighting value, must be 'on' or 'off'");
                    return;
            }
        }

        [AttributeParser("scene_blend", Pass)] 
        public static void ParseSceneBlend(string[] values, Material material) {           
            switch (values.Length) { 
                case 1: 
                    // e.g. scene_blend add 
                    // lookup the real enum equivalent to the script value 
                    object val = ScriptEnumAttribute.Lookup(values[0], typeof(SceneBlendType)); 
          
                    // if a value was found, assign it 
                    if(val != null) 
                        material.SetSceneBlending((SceneBlendType)val); 
                    else 
                        ParseHelper.LogParserError("scene_blend", material.Name, "Invalid enum value"); 
                    break; 
                case 2: 
                    // e.g. scene_blend source_alpha one_minus_source_alpha  
                    // lookup the real enums equivalent to the script values 
                    object src_val = ScriptEnumAttribute.Lookup(values[0], typeof(SceneBlendFactor)); 
                    object dest_val = ScriptEnumAttribute.Lookup(values[1], typeof(SceneBlendFactor)); 
       
                    // if both values were found, assign them 
                    if(src_val != null && dest_val != null) 
                        material.SetSceneBlending((SceneBlendFactor)src_val,(SceneBlendFactor)dest_val); 
                    else 
                        if (src_val == null) 
                        ParseHelper.LogParserError("scene_blend", material.Name, "Invalid enum value: " + values[0].ToString()); 
                    if (dest_val == null) 
                        ParseHelper.LogParserError("scene_blend", material.Name, "Invalid enum value: " + values[1].ToString()); 
                    break; 
                default: 
                    material.SetSceneBlending(SceneBlendFactor.Zero,SceneBlendFactor.Zero); 
                    ParseHelper.LogParserError("scene_blend", material.Name, "Expected 1 or 2 params."); 
                    return; 
            } 
        } 

        [AttributeParser("cull_hardware", Pass)]
        public static void ParseCullHardware(string[] values, Pass pass) {
            if(values.Length != 1) {
                ParseHelper.LogParserError("cull_hardware", pass.Parent.Name, "Expected 2 params.");
                return;
            }

            // lookup the real enum equivalent to the script value
            object val = ScriptEnumAttribute.Lookup(values[0], typeof(CullingMode));

            // if a value was found, assign it
            if(val != null)
                pass.CullMode = (CullingMode)val;
            else
                ParseHelper.LogParserError("cull_hardware", pass.Parent.Name, "Invalid enum value");
        }

        [AttributeParser("cull_software", Pass)]
        public static void ParseCullSoftware(string[] values, Pass pass) {
            if(values.Length != 1) {
                ParseHelper.LogParserError("cull_software", pass.Parent.Name, "Invalid enum value");
                return;
            }

            // lookup the real enum equivalent to the script value
            object val = ScriptEnumAttribute.Lookup(values[0], typeof(ManualCullingMode));

{'fraction_non_alphanumeric': 0.09322970315572619, 'fraction_numerical': 0.004307519430658302, 'mean_word_length': 1.407285514629638, 'pattern_counts': {'":': 20, '<': 109, '<?xml version=': 0, '>': 95, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '809555', 'n_tokens_mistral': 12855, 'n_tokens_neox': 11862, 'n_words': 3244}