/// <summary>
        /// Saves the beatmap
        /// </summary>
        /// <param name="filename">The file to save the beatmap as</param>
        public void Save(string filename)
        {
            WriteBuffer.Clear();
            SectionLength.Clear();

            CultureInfo lastCulture = Thread.CurrentThread.CurrentCulture;

            Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US", false);
            Save("", "osu file format v14");
            FieldInfo[] newFields = GetType().GetFields();
            FieldInfo[] oldFields = Info.GetType().GetFields();

            foreach (FieldInfo f1 in newFields)
            {
                foreach (FieldInfo f2 in oldFields.Where(f2 => f1.Name == f2.Name))
                {
                    switch (f1.Name)
                    {
                    case "EditorBookmarks":
                    {
                        List <int> temps = (List <int>)f1.GetValue(this);
                        if (temps.Count != 0)
                        {
                            Save("General", "EditorBookmarks:" + string.Join(",", temps.Select(t => t.ToString(CultureInfo.InvariantCulture)).ToArray()));
                        }
                    }
                    break;

                    case "Bookmarks":
                    {
                        List <int> temps = (List <int>)f1.GetValue(this);
                        if (temps.Count != 0)
                        {
                            Save("Editor", "Bookmarks:" + string.Join(",", temps.Select(t => t.ToString(CultureInfo.InvariantCulture)).ToArray()));
                        }
                    }
                    break;

                    case "Tags":
                    {
                        List <string> temps = (List <string>)f1.GetValue(this);
                        if (temps.Count != 0)
                        {
                            Save("Metadata", "Tags:" + string.Join(" ", temps.ToArray()));
                        }
                    }
                    break;

                    case "Events":
                        foreach (EventBase o in (IEnumerable <EventBase>)f1.GetValue(this))
                        {
                            if (o.GetType() == typeof(ContentEvent))
                            {
                                ContentEvent backgroundInfo = (ContentEvent)o;
                                Save("Events", "0," + o.StartTime + ",\"" + backgroundInfo.Filename + "\"" + ",0,0");
                            }
                            else if (o.GetType() == typeof(BreakEvent))
                            {
                                BreakEvent breakInfo = (BreakEvent)o;
                                Save("Events", "2," + o.StartTime + "," + breakInfo.EndTime);
                            }
                            else if (o.GetType() == typeof(BackgroundColourEvent))
                            {
                                BackgroundColourEvent colourInfo = (BackgroundColourEvent)o;
                                Save("Events", "3," + o.StartTime + "," + colourInfo.Colour.R + "," + colourInfo.Colour.G + "," + colourInfo.Colour.B);
                            }
                        }
                        break;

                    case "TimingPoints":
                    {
                        foreach (TimingPoint o in (IEnumerable <TimingPoint>)f1.GetValue(this))
                        {
                            Save("TimingPoints", o.Time + "," + o.BpmDelay + "," + o.TimeSignature + "," + o.SampleSet + "," + o.CustomSampleSet + "," + o.VolumePercentage + "," + Convert.ToInt32(!o.InheritsBPM) + "," + (int)o.VisualOptions);
                        }
                    }
                    break;

                    case "ComboColours":
                    {
                        foreach (Combo o in (IEnumerable <Combo>)f1.GetValue(this))
                        {
                            Save("Colours", "Combo" + o.ComboNumber + ':' + o.Colour.R + "," + o.Colour.G + "," + o.Colour.B);
                        }
                    }
                    break;

                    case "SliderBorder":
                    {
                        /*if (f1.GetValue(this) == f2.GetValue(Info))
                         *  continue;
                         * Colour o = (Colour)f1.GetValue(this);
                         * Save("Colours", "SliderBorder: " + o.R + "," + o.G + "," + o.B);*/
                    }
                    break;

                    case "HitObjects":
                        foreach (CircleObject obj in (IEnumerable <CircleObject>)f1.GetValue(this))
                        {
                            if (obj.GetType() == typeof(CircleObject))
                            {
                                Save("HitObjects", obj.Location.X + "," + obj.Location.Y + "," + obj.StartTime + "," + (int)obj.Type + "," + (int)obj.Effect + ",0:0:0:0:");
                            }
                            else if (obj.GetType() == typeof(SliderObject))
                            {
                                SliderObject sliderInfo  = (SliderObject)obj;
                                string       pointString = sliderInfo.Points.Aggregate("", (current, p) => current + ("|" + p.X + ':' + p.Y));
                                Save("HitObjects", obj.Location.X + "," + obj.Location.Y + "," + obj.StartTime + "," + (int)obj.Type + "," + (int)obj.Effect + "," + sliderInfo.Type.ToString().Substring(0, 1) + pointString + "," + sliderInfo.RepeatCount + "," + sliderInfo.MaxPoints);
                            }
                            else if (obj.GetType() == typeof(SpinnerObject))
                            {
                                SpinnerObject spinnerInfo = (SpinnerObject)obj;
                                Save("HitObjects", obj.Location.X + "," + obj.Location.Y + "," + obj.StartTime + "," + (int)obj.Type + "," + (int)obj.Effect + "," + spinnerInfo.EndTime);
                            }
                        }
                        break;

                    default:
                        if (f1.Name != "Format" && f1.Name != "Filename" && f1.Name != "BeatmapHash" && f1.Name != "Folder")
                        {
                            if (f1.GetValue(this) != null)
                            {
                                if (f2.GetValue(Info) != null)
                                {
                                    if ((f1.GetValue(this).GetType() == typeof(GameMode)) || (f1.GetValue(this).GetType() == typeof(OverlayOptions)))
                                    {
                                        Save(GetSection(f1.Name), f1.Name + ":" + (int)f1.GetValue(this));
                                    }
                                    else
                                    {
                                        Save(GetSection(f1.Name), f1.Name + ":" + f1.GetValue(this));
                                    }
                                }

                                /*else
                                 * {
                                 *  if((f2.GetValue(Info).GetType() == typeof(GameMode)) || (f2.GetValue(Info).GetType() == typeof(OverlayOptions)))
                                 *      Save(GetSection(f2.Name), f2.Name + ":" + (int)f2.GetValue(Info));
                                 *  else
                                 *      Save(GetSection(f2.Name), f2.Name + ":" + f2.GetValue(Info));
                                 * }*/
                            }
                        }
                        break;
                    }
                }
            }
            foreach (PropertyInfo f1 in GetType().GetProperties())
            {
                foreach (PropertyInfo f2 in Info.GetType().GetProperties().Where(f2 => f1.Name == f2.Name))
                {
                    if (f1.GetValue(this, null) != null)
                    {
                        if (f2.GetValue(Info, null) != null)
                        {
                            if ((f1.GetValue(this, null).GetType() == typeof(GameMode)) || (f1.GetValue(this, null).GetType() == typeof(OverlayOptions)))
                            {
                                Save(GetSection(f1.Name), f1.Name + ':' + (int)f1.GetValue(this, null));
                            }
                            else
                            {
                                Save(GetSection(f1.Name), f1.Name + ':' + f1.GetValue(this, null));
                            }
                        }
                        else
                        {
                            if ((f2.GetValue(Info, null).GetType() == typeof(GameMode)) || (f2.GetValue(Info, null).GetType() == typeof(OverlayOptions)))
                            {
                                Save(GetSection(f2.Name), f2.Name + ':' + (int)f2.GetValue(Info, null));
                            }
                            else
                            {
                                Save(GetSection(f2.Name), f2.Name + ':' + f2.GetValue(Info, null));
                            }
                        }
                    }
                }
            }
            FinishSave(filename);
            Thread.CurrentThread.CurrentCulture = lastCulture;
        }
        private void Parse(string bm)
        {
            FileInfo ffii = new FileInfo(bm);

            Info.Folder      = ffii.DirectoryName;
            Info.Filename    = bm;
            Info.BeatmapHash = MD5FromFile(bm);
            using (StreamReader sR = new StreamReader(bm))
            {
                string currentSection = "";

                while (sR.Peek() != -1)
                {
                    string line = sR.ReadLine();

                    //Check for section tag
                    if (line.StartsWith("["))
                    {
                        currentSection = line;
                        continue;
                    }

                    //Check for commented-out line
                    //or blank lines
                    if (line.StartsWith("//") || line.Length == 0)
                    {
                        continue;
                    }

                    //Check for version string
                    if (line.StartsWith("osu file format"))
                    {
                        Info.Format = Convert.ToInt32(line.Substring(17).Replace(Environment.NewLine, "").Replace(" ", ""));
                    }

                    //Do work for [General], [Metadata], [Difficulty] and [Editor] sections
                    if ((currentSection == "[General]") || (currentSection == "[Metadata]") || (currentSection == "[Difficulty]") || (currentSection == "[Editor]"))
                    {
                        string[] reSplit   = line.Split(':');
                        string   cProperty = reSplit[0].TrimEnd();

                        bool isValidProperty = false;
                        foreach (string k in BM_Sections.Keys)
                        {
                            if (k.Contains(cProperty))
                            {
                                isValidProperty = true;
                            }
                        }
                        if (!isValidProperty)
                        {
                            continue;
                        }

                        //Check for blank value
                        string cValue = reSplit[1].Trim();

                        //Import properties into Info
                        switch (cProperty)
                        {
                        case "EditorBookmarks":
                        {
                            string[] marks = cValue.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries);
                            foreach (string m in marks.Where(m => m != ""))
                            {
                                Info.EditorBookmarks.Add(Convert.ToInt32(m));
                            }
                        }
                        break;

                        case "Bookmarks":
                        {
                            string[] marks = cValue.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries);
                            foreach (string m in marks.Where(m => m != ""))
                            {
                                Info.Bookmarks.Add(Convert.ToInt32(m));
                            }
                        }
                        break;

                        case "Tags":
                            string[] tags = cValue.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                            foreach (string t in tags)
                            {
                                Info.Tags.Add(t);
                            }
                            break;

                        case "Mode":
                            Info.Mode = (GameMode)Convert.ToInt32(cValue);
                            break;

                        case "OverlayPosition":
                            Info.OverlayPosition = (OverlayOptions)Enum.Parse(typeof(OverlayOptions), cValue);
                            break;

                        case "AlwaysShowPlayfield":
                            Info.AlwaysShowPlayfield = Convert.ToBoolean(Convert.ToInt32(cValue));
                            break;

                        default:
                            FieldInfo    fi = Info.GetType().GetField(cProperty);
                            PropertyInfo pi = Info.GetType().GetProperty(cProperty);
                            if (fi != null)
                            {
                                if (fi.FieldType == typeof(float?))
                                {
                                    fi.SetValue(Info, (float?)Convert.ToDouble(cValue));
                                }
                                if (fi.FieldType == typeof(float))
                                {
                                    fi.SetValue(Info, (float)Convert.ToDouble(cValue));
                                }
                                else if ((fi.FieldType == typeof(int?)) || (fi.FieldType == typeof(int)))
                                {
                                    fi.SetValue(Info, Convert.ToInt32(cValue));
                                }
                                else if (fi.FieldType == typeof(string))
                                {
                                    fi.SetValue(Info, cValue);
                                }
                                break;
                            }
                            if (pi.PropertyType == typeof(float?))
                            {
                                pi.SetValue(Info, (float?)Convert.ToDouble(cValue), null);
                            }
                            if (pi.PropertyType == typeof(float))
                            {
                                pi.SetValue(Info, (float)Convert.ToDouble(cValue), null);
                            }
                            else if ((pi.PropertyType == typeof(int?)) || (pi.PropertyType == typeof(int)))
                            {
                                pi.SetValue(Info, Convert.ToInt32(cValue), null);
                            }
                            else if (pi.PropertyType == typeof(string))
                            {
                                pi.SetValue(Info, cValue, null);
                            }
                            break;
                        }
                        continue;
                    }

                    //The following are version-dependent, the version is stored as a numeric value inside Info.Format
                    //Do work for [Events] section
                    if (currentSection == "[Events]")
                    {
                        string[] reSplit = line.Split(',');
                        switch (reSplit[0].ToLower())
                        {
                        case "0":
                        case "1":
                        case "video":
                            Info.Events.Add(new ContentEvent
                            {
                                Type      = reSplit[0].ToLower() == "1" || reSplit[0].ToLower() == "video" ? ContentType.Video : ContentType.Image,
                                StartTime = Convert.ToInt32(reSplit[1]),
                                Filename  = reSplit[2].Replace("\"", "")
                            });
                            break;

                        case "2":
                            Info.Events.Add(new BreakEvent
                            {
                                StartTime = Convert.ToInt32(reSplit[1]),
                                EndTime   = Convert.ToInt32(reSplit[2])
                            });
                            break;

                        case "3":
                            Info.Events.Add(new BackgroundColourEvent
                            {
                                StartTime = Convert.ToInt32(reSplit[1]),
                                Colour    = new Colour
                                {
                                    R = Convert.ToInt32(reSplit[2]),
                                    G = Convert.ToInt32(reSplit[3]),
                                    B = Convert.ToInt32(reSplit[4])
                                },
                            });
                            break;
                        }
                    }

                    //Do work for [TimingPoints] section
                    if (currentSection == "[TimingPoints]")
                    {
                        TimingPoint tempTimingPoint = new TimingPoint();

                        float[]  values  = { 0, 0, 4, 0, 0, 100, 0, 0, 0 };
                        string[] reSplit = line.Split(',');
                        for (int i = 0; i < reSplit.Length; i++)
                        {
                            values[i] = (float)Convert.ToDouble(reSplit[i]);
                        }
                        tempTimingPoint.InheritsBPM = !Convert.ToBoolean(Convert.ToInt32(values[6]));
                        tempTimingPoint.beatLength  = values[1];
                        if (values[1] > 0)
                        {
                            tempTimingPoint.bpm = Math.Round(60000 / tempTimingPoint.beatLength);
                        }
                        else if (values[1] < 0)
                        {
                            tempTimingPoint.velocity = Math.Abs(100 / tempTimingPoint.beatLength);
                        }
                        tempTimingPoint.Time             = (float)Convert.ToDouble(values[0]);
                        tempTimingPoint.BpmDelay         = (float)Convert.ToDouble(values[1]);
                        tempTimingPoint.TimeSignature    = Convert.ToInt32(values[2]);
                        tempTimingPoint.SampleSet        = Convert.ToInt32(values[3]);
                        tempTimingPoint.CustomSampleSet  = Convert.ToInt32(values[4]);
                        tempTimingPoint.VolumePercentage = Convert.ToInt32(values[5]);
                        tempTimingPoint.VisualOptions    = (TimingPointOptions)Convert.ToInt32(values[7]);
                        Info.TimingPoints.Add(tempTimingPoint);
                        this.TimingPoints.Add(tempTimingPoint);
                    }
                    for (int i = 1, l = TimingPoints.Count; i < l; i++)
                    {
                        if (TimingPoints[i].bpm == 0)
                        {
                            TimingPoints[i].beatLength = TimingPoints[i - 1].beatLength;
                            TimingPoints[i].bpm        = TimingPoints[i - 1].bpm;
                        }
                    }

                    //Do work for [Colours] section
                    if (currentSection == "[Colours]")
                    {
                        string   property = line.Substring(0, line.IndexOf(':', 1)).Trim();
                        string   value    = line.Substring(line.IndexOf(':', 1) + 1).Trim();
                        string[] reSplit  = value.Split(',');

                        if (property.Length > 5 && property.Substring(0, 5) == "Combo")
                        {
                            Combo newCombo = new Combo
                            {
                                Colour = new Colour
                                {
                                    R = Convert.ToInt32(reSplit[0]),
                                    G = Convert.ToInt32(reSplit[1]),
                                    B = Convert.ToInt32(reSplit[2])
                                }
                            };
                            try
                            {
                                newCombo.ComboNumber = Convert.ToInt32(property.Substring(5, 1));
                            }
                            catch
                            {
                                Debug.Assert(false, "Invalid combonumber at index 5. " + line);
                                continue;
                            }
                        }
                        else if (property.Length > 5 && property == "SliderBorder")
                        {
                            Info.SliderBorder = new Colour
                            {
                                R = Convert.ToInt32(reSplit[0]),
                                G = Convert.ToInt32(reSplit[1]),
                                B = Convert.ToInt32(reSplit[2])
                            };
                        }
                    }

                    //Do work for [HitObjects] section
                    if (currentSection == "[HitObjects]")
                    {
                        string[]     reSplit   = line.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                        CircleObject newObject = new CircleObject
                        {
                            Radius    = (float)(54.42 - 4.48 * Info.CircleSize),
                            Location  = new Point2(Convert.ToInt32(reSplit[0]), Convert.ToInt32(reSplit[1])),
                            StartTime = (float)Convert.ToDouble(reSplit[2]),
                            Type      = (HitObjectType)Convert.ToInt32(reSplit[3]),
                            Effect    = (EffectType)Convert.ToInt32(reSplit[4])
                        };
                        if ((newObject.Type & HitObjectType.Slider) > 0)
                        {
                            newObject = new SliderObject(newObject);
                            ((SliderObject)newObject).Velocity = Info.SliderMultiplier * TimingPointByTime(newObject.StartTime).SliderBpm / 600f;
                            switch (reSplit[5].Substring(0, 1))
                            {
                            case "B":
                                ((SliderObject)newObject).Type = SliderType.Bezier;
                                break;

                            case "C":
                                ((SliderObject)newObject).Type = SliderType.CSpline;
                                break;

                            case "L":
                                //((SliderObject)newObject).Type = SliderType.Linear;
                                ((SliderObject)newObject).Type = SliderType.Bezier;
                                break;

                            case "P":
                                ((SliderObject)newObject).Type = SliderType.PSpline;
                                break;
                            }
                            string[] pts = reSplit[5].Split(new[] { "|" }, StringSplitOptions.None);

                            ((SliderObject)newObject).Points.Add(newObject.Location + new Point2());

                            //Always exclude index 1, this will contain the type
                            for (int i = 1; i <= pts.Length - 1; i++)
                            {
                                Point2 p = new Point2((float)Convert.ToDouble(pts[i].Substring(0, pts[i].IndexOf(":", StringComparison.InvariantCulture))),
                                                      (float)Convert.ToDouble(pts[i].Substring(pts[i].IndexOf(":", StringComparison.InvariantCulture) + 1)));
                                ((SliderObject)newObject).Points.Add(p);
                            }

                            /*
                             * var pxPerBeat      = beatmap.SliderMultiplier * 100 * timing.velocity;
                             * var beatsNumber    = (hitObject.pixelLength * hitObject.repeatCount) / pxPerBeat;
                             * hitObject.duration = Math.ceil(beatsNumber * timing.beatLength);
                             * hitObject.endTime  = hitObject.startTime + hitObject.duration;
                             */

                            ((SliderObject)newObject).RepeatCount = Convert.ToInt32(reSplit[6]);
                            ((SliderObject)newObject).PixelLength = Convert.ToSingle(reSplit[7]);
                            float tempMaxPoints;
                            if (float.TryParse(reSplit[7], out tempMaxPoints))
                            {
                                ((SliderObject)newObject).MaxPoints = tempMaxPoints;
                            }
                            ((SliderObject)newObject).CreateCurves();

                            var timing      = TimingPointByTime(newObject.StartTime);
                            var pxPerBeat   = Info.SliderMultiplier * 100 * timing.velocity;
                            var beatsNumber = ((SliderObject)newObject).PixelLength * ((SliderObject)newObject).RepeatCount / pxPerBeat;
                            var duration    = (int)Math.Ceiling(beatsNumber * timing.beatLength);
                            ((SliderObject)newObject).duration = duration;
                        }
                        if ((newObject.Type & HitObjectType.Spinner) > 0)
                        {
                            newObject = new SpinnerObject(newObject);
                            ((SpinnerObject)newObject).EndTime = (float)Convert.ToDouble(reSplit[5]);
                        }
                        Info.HitObjects.Add(newObject);
                    }
                }
            }

            //Copy the fields/properties of Info locally
            foreach (FieldInfo fi in Info.GetType().GetFields())
            {
                FieldInfo ff = GetType().GetField(fi.Name);
                ff.SetValue(this, fi.GetValue(Info));
            }
            foreach (PropertyInfo pi in Info.GetType().GetProperties())
            {
                PropertyInfo ff = GetType().GetProperty(pi.Name);
                ff.SetValue(this, pi.GetValue(Info, null), null);
            }
        }