using System;
using System.Linq;
using System.Windows.Forms;
using System.IO;
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Diagnostics;

namespace UFO_Classes
{
    public partial class UFO_Classes : Form 
    {
        public Collection<SavedGame> saved_games = new Collection<SavedGame>();

        string file_needs_update;

        private void init()
        {
            timer.Interval = GlobalVariables.sleep_time_before_first_file_open; //time [ms] before saved game is updated after game writes to file, should give the game able time, and should be less than it takes for a player to save then load the game
            fileSystemWatcher.Path = current_dir();
            load_saved_games_collection();

            //temp

            foreach (SavedGame saved_game in saved_games)
            {
                update_list_box_3(saved_game);
                //update_list_box_6(saved_game); 
            }

            update_list_box_4(saved_games);

            update_list_box_2();

            //temp
        }

        private void run_game()
        {
            string[] shortcuts = System.IO.Directory.GetFiles(current_dir(), "*.lnk", SearchOption.TopDirectoryOnly);

            foreach (string shortcut in shortcuts)
            {
                Process.Start(shortcut);
            }
        }

        private string current_dir()
        {
            if (RunConditions.use_predefined_directory)
            {
                return @"C:\Program Files\Steam\steamapps\common\xcom ufo defense\XCOM";
            }
            else
            {
                return Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase).ToString().Remove(0, 6) + @"\"; //removes the first 6 chars as they will always have the value "File: " or something like that, can't remember
            }
        }

        private void load_saved_games_collection()
        {
            if (RunConditions.test_active)
            {
                saved_games.Add(new SavedGame("", SavedGame.UpdateFile.Yes));
            }
            else
            {
                foreach (string filepath in System.IO.Directory.GetFiles(current_dir(), "soldier.dat", SearchOption.AllDirectories))
                {
                    if (!filepath.Contains("MISSDAT")) //non save game directory with soldier.dat file in it
                    {
                        saved_games.Add(new SavedGame(filepath, SavedGame.UpdateFile.No));
                    }
                }
            }
        }

//////////////////// events /////////////////////////

        public UFO_Classes()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            init();
            if (RunConditions.test_active == false && RunConditions.run_shortcuts) run_game();
        }

        private void fileSystemWatcher_Changed(object sender, FileSystemEventArgs e) 
        {
            if (e.FullPath.Contains("MISSDAT") == false)
            {
                fileSystemWatcher.EnableRaisingEvents = false;        

                file_needs_update = e.FullPath;

                timer.Enabled = true;
            }
        }

        private void timer_Tick(object sender, EventArgs e)
        {
            timer.Enabled = false;

////////////////////////// function body ///////////////////////////////////    

            if (file_just_created_flag)
            {
                saved_games.Add(new SavedGame(file_needs_update, SavedGame.UpdateFile.No));
                file_just_created_flag = false;
            }

            foreach (SavedGame saved_game in saved_games) //must be a way to do this thats not homebrewed
            {
                if (saved_game.file_path.Equals(file_needs_update))
                {
                    saved_game.update(SavedGame.UpdateFile.Yes);

                    //temp

                    if (saved_game.is_geoscape_save)
                    {
                        listBox3.Items.Clear();

                        update_list_box_3(saved_game);

                        listBox6.Items.Clear();

                        update_list_box_6(saved_game);

                            Collection<SavedGame> saved_games_2 = new Collection<SavedGame>();

                            saved_games_2.Add(saved_game);

                            update_list_box_4(saved_games_2);
                    }

                    update_list_box_2();

                    if(saved_game.is_geoscape_save) listBox1.Items.Add("Geoscape save\t" + saved_game.file_path);
                    else                            listBox1.Items.Add("Mission save \t" + saved_game.file_path);

                    //temp

                    break;
                }
            }

////////////////////////// function body ///////////////////////////////////    

            fileSystemWatcher.EnableRaisingEvents = true;
        }

        private bool file_just_created_flag = false;

        private void fileSystemWatcher_Created(object sender, FileSystemEventArgs e) //OBS! When a file is created this event will trigger, THEN just after that the fileSystemWatcher_Changed event will trigger 
        {
            if (e.FullPath.Contains("MISSDAT") == false)
            {
                file_just_created_flag = true;
            }
        }

        ///// debug output ///// code here has alot lower standards.. 

        private void update_list_box_3(SavedGame saved_game) //temp
        {
            if (RunConditions.test_active && RunConditions.if_true_large_sample_if_false_just_the_one_soldier) return;

            foreach (Soldier soldier in saved_game.soldiers)
            {
                string name = "";

                for (int i = 0; i < soldier.name.Length; i++)
                {
                    name += Convert.ToChar(soldier.name[i]);
                }

                string Class = "";

                for (int i = 0; i < soldier.Class_to_byte_array().Length; i++)
                {
                    Class += Convert.ToChar(soldier.Class_to_byte_array()[i]);
                }

                string level;

                if (soldier.Level == -1) level = soldier.did_not_qualify_for_a_class_marker.ToString();
                else level = soldier.Level.ToString();

                string output = "";

                output += Class + "\t";
                output += level + "\t";

                     if (soldier.UserDeterminedClass == 1) output += "| Static" + "\t";
                else if (soldier.UserDeterminedClass == 2) output += "= Semi  " + "\t";
                else                                       output += "- Fluent" + "\t";

                if (soldier.alive)  output += "Alive" + "\t";
                else                output += "Dead " + "\t";

                if (soldier.in_transit) output += "In transit   " + "\t";
                else                    output += "On base      " + "\t";

                output += soldier.energy_regen.ToString() + "\t";

                if(!soldier.psi_locked)
                {
                    output += "PST: " + soldier.psi_strength          + "\t";
                    output += "PSK: " + soldier.psi_skill             + "\t";
                    output += "PAS: " + soldier.psi_attack_strength   + " ; ";
                    output += (Math.Round(soldier.psi_atk_same_format_as_equalized_stats / GlobalVariables.average_of_spreads * 100, 1)).ToString() + "%\t";
                    output += "PDS: " + soldier.psi_defense_strength  + " ; ";
                    output += (Math.Round(soldier.psi_def_same_format_as_equalized_stats / GlobalVariables.average_of_spreads * 100, 1)).ToString() + "%\t";
                }
                else
                {
                    output += "\t\t\t\t\t\t";
                }

                for (int i = 0; i < 8; i++)
                {
                    output += soldier.stats_original[i].ToString() + " ";
                }

                output += "\t";

                for (int i = 0; i < 8; i++)
                {
                    output += Math.Round(soldier.stats_equalized[i]/GlobalVariables.average_of_spreads*100,1).ToString() + "\t";
                }

                output += name;

                output += "     \t";

                output += saved_game.name;

                listBox3.Items.Add(output);
            }
        }

        private void update_list_box_4(Collection<SavedGame> saved_games) //temp
        {
            listBox4.Items.Clear();

            double[] amount_of_times_class_hit = new double[GlobalVariables.classes.Length];

            double[,,] avg_stats = new double[GlobalVariables.classes.Length, 8, 2];

            foreach (SavedGame savedgame in saved_games) foreach(Soldier soldier in savedgame.soldiers)
            {
                if (soldier.Level >= RunConditions.listbox4_soldier_level && RunConditions.if_true_at_level_if_false_at_or_above == false)
                {
                    amount_of_times_class_hit[soldier.chosen_class_number]++;

                    for (int j = 0; j < 8; j++)
                    {
                        avg_stats[soldier.chosen_class_number, j, 0] += soldier.stats_equalized[j];
                        avg_stats[soldier.chosen_class_number, j, 1] += soldier.stats_original[j];
                    }
                }
                else if (soldier.Level == RunConditions.listbox4_soldier_level && RunConditions.if_true_at_level_if_false_at_or_above == true)
                {
                    amount_of_times_class_hit[soldier.chosen_class_number]++;

                    for (int j = 0; j < 8; j++)
                    {
                        avg_stats[soldier.chosen_class_number, j, 0] += soldier.stats_equalized[j];
                        avg_stats[soldier.chosen_class_number, j, 1] += soldier.stats_original[j];
                    }
                }
            }

            ///// code used to determine recruit level distribution

            if (RunConditions.test_active)
            {
                Collection<double> chance_of_hitting_level = new Collection<double>();

                int level = -1;

                int amount_of_times_level_hit;

                while (level < 10)
                {
                    amount_of_times_level_hit = 0;

                    foreach (SavedGame savedgame in saved_games) foreach (Soldier soldier in savedgame.soldiers)
                    {
                        if (soldier.Level == level)
                        {
                            amount_of_times_level_hit++;
                        }
                    }

                    chance_of_hitting_level.Add(amount_of_times_level_hit / Convert.ToDouble(RunConditions.number_of_soldiers_to_test) * 100);

                    level++;

                    if (level == 0) level++;
                }

                level = 0;

                foreach (double value in chance_of_hitting_level)
                {
                    int a = RunConditions.number_of_soldiers_to_test/10;

                    double b = (value / 100.0 * RunConditions.number_of_soldiers_to_test);

                    while(b>10)
                    {
                        b /= 10.0;
                        a /= 10;
                    }
                    
                    listBox6.Items.Add((level++).ToString() + ".        " + Math.Round(value,2).ToString() + "%\t" + (value / 100.0 * RunConditions.number_of_soldiers_to_test).ToString() );//+ "           \t: 1/" + (Math.Round((RunConditions.number_of_soldiers_to_test/(value / 100.0 * RunConditions.number_of_soldiers_to_test))/a,1)*a).ToString());
                }
            }

            /////

            for (int i = 0; i < GlobalVariables.classes.Length; i++) for (int j = 0; j < 8; j++)
            {
                avg_stats[i, j, 0] /= amount_of_times_class_hit[i];
                avg_stats[i, j, 1] /= amount_of_times_class_hit[i];
            }

            for (int i = 0; i < GlobalVariables.classes.Length; i++)
            {
                string output = "";

                output += GlobalVariables.classes[i] + "\t";
                output += amount_of_times_class_hit[i].ToString() + "\t";
                output += (Math.Round((amount_of_times_class_hit[i] / ((double)amount_of_times_class_hit.Sum()) * 100) * 10, 1) / 10.0).ToString() + "%\t\t";
                
                    for (int j = 0; j < 8; j++)
                    {
                        output += Math.Round(avg_stats[i, j, 0]/GlobalVariables.average_of_spreads*100,1).ToString() + "\t";
                    }

                output += "\t";

                    for (int j = 0; j < 8; j++)
                    {
                        output += Math.Round(avg_stats[i, j, 1], 1).ToString() + "\t";
                    }

                listBox4.Items.Add(output);
            }
        }

        private void update_list_box_2() //temp
        {
            if (RunConditions.test_active) return;

            listBox2.Items.Clear();
            foreach (SavedGame saved_game in saved_games)
            {
                string output = "";

                    if(saved_game.is_geoscape_save) output += "Geoscape save\t";
                    else                            output += "Mission save\t";

                output += saved_game.file_path;

                listBox2.Items.Add(output);
            }
        }

        private void update_list_box_6(SavedGame saved_game) //temp
        {
            if (RunConditions.test_active) return;
           
            listBox6.Items.Add("       " + (Math.Floor((saved_game.mean) * 1000) / 1000).ToString() + " :Mean");
            listBox6.Items.Add("     " + (Math.Floor((saved_game.median) * 1000) / 1000).ToString() + " :Median");
            listBox6.Items.Add("        " + (Math.Floor((Math.Sqrt(saved_game.variance) * 1000)) / 1000).ToString() + " :STD");
            listBox6.Items.Add("     " + (Math.Floor((saved_game.variance) * 1000) / 1000).ToString() + " :Variance");

            double[] levels = new double[10];

            List<int> soldier_levels = new List<int>(); 

            foreach (Soldier soldier in saved_game.soldiers)
            {
                if (soldier.alive) soldier_levels.Add(soldier.Level);
            }

            foreach (int level in soldier_levels)
            {
                if (level == -1)
                {
                    levels[0]++;
                }
                else
                {
                    levels[level]++;
                }
            }
            
            string temp;

            for (int i = 0; i < 10; i++)
            {
                temp = "";

                listBox6.Items.Add("");

                temp += levels[i].ToString();

                temp += "\t";

                temp += (Math.Floor((levels[i] / soldier_levels.Count * 100) * 10) / 10).ToString();

                temp += "%\t.";

                if (i == 0)
                {
                    temp += "x";
                }
                else
                {
                    temp += i.ToString();
                }

                listBox6.Items.Add(temp);
            }
        }

        private void listBox3_SelectedIndexChanged(object sender, EventArgs e)
        {
            Collection<Soldier> soldiers = new Collection<Soldier>();
            Collection<string> saved_game_paths = new Collection<string>();

            if (listBox3.SelectedIndex != -1)
            {
                foreach (SavedGame saved_game in saved_games) foreach (Soldier soldier in saved_game.soldiers)
                {
                    string name = "";

                    for (int i = 0; i < soldier.name.Length; i++)
                    {
                        name += Convert.ToChar(soldier.name[i]);
                    }

                    if(listBox3.SelectedItem.ToString().Contains(name)                                 &&
                       listBox3.SelectedItem.ToString().Contains(soldier.stats_original[0].ToString()) &&
                       listBox3.SelectedItem.ToString().Contains(soldier.stats_original[1].ToString()) &&
                       listBox3.SelectedItem.ToString().Contains(soldier.stats_original[2].ToString()) &&
                       listBox3.SelectedItem.ToString().Contains(soldier.stats_original[3].ToString()) &&
                       listBox3.SelectedItem.ToString().Contains(soldier.stats_original[4].ToString()) &&
                       listBox3.SelectedItem.ToString().Contains(soldier.stats_original[5].ToString()) &&
                       listBox3.SelectedItem.ToString().Contains(soldier.stats_original[6].ToString()) &&
                       listBox3.SelectedItem.ToString().Contains(soldier.stats_original[7].ToString()) &&
                       listBox3.SelectedItem.ToString().Contains(saved_game.name))
                    {
                        soldiers.Add(soldier);
                        saved_game_paths.Add(saved_game.file_path);
                    }
                }

                listBox5.Items.Clear();

                if (soldiers.Count == 1)
                {
                    for (int i = 0; i < GlobalVariables.classes.Length; i++)
                    {
                        listBox5.Items.Add(GlobalVariables.classes[i] + "\t" + Math.Round(soldiers.ElementAt(0).calculate_level(soldiers.ElementAt(0).class_scores_used_for_level[i], true), 3).ToString() + "\t" + Math.Round(soldiers.ElementAt(0).class_scores_used_for_class[i], 3).ToString() + "\t" + Math.Round(soldiers.ElementAt(0).class_score_used_for_class_altered_with_requirement_values[i], 3).ToString());

                        if (i == soldiers.ElementAt(0).chosen_class_number)
                        {
                            listBox5.SelectedIndex = i;
                        }
                    }
                }
                else
                {
                    listBox5.Items.Add("Warning, multiple versions of the same soldier found.");

                    for (int j = 0; j < soldiers.Count; j++)
                    {
                        listBox5.Items.Add("");
                        listBox5.Items.Add("From saved game at path: " + saved_game_paths.ElementAt(j));
                        for (int i = 0; i < GlobalVariables.classes.Length; i++)
                        {
                            listBox5.Items.Add(GlobalVariables.classes[i] + "\t" + Math.Round(soldiers.ElementAt(j).calculate_level(soldiers.ElementAt(j).class_scores_used_for_level[i], true), 3).ToString() + "\t" + Math.Round(soldiers.ElementAt(j).class_scores_used_for_class[i], 3).ToString() + "\t" + Math.Round(soldiers.ElementAt(j).class_score_used_for_class_altered_with_requirement_values[i], 3).ToString());
                        }
                    }
                }
            }
        }
    }

    public static class RunConditions 
    {
        //general

        public const bool use_predefined_directory = false;  //release: false
        public const bool run_shortcuts = true; //release: true

        //test specifics

        public const bool test_active = false; //release: false
        
        public const bool if_true_at_level_if_false_at_or_above = false; //release: Don't care
        public const int listbox4_soldier_level = 1; //release: 1 // if -1, all soldiers 
                
        public const bool if_true_large_sample_if_false_just_the_one_soldier = true; //release: Don't care 
        public const int soldier_type = 1; //realse: Don't care  //0 = worst new | 1 = best new | 2 = worst high | 3 = best high | 4 = min init stats up to max init
        public const bool if_true_psi_skill_combat_cap_if_false_lab_cap = false; //release: Don't care

        public static int number_of_soldiers_to_test = Convert.ToInt32(Math.Pow(10, 6)*1.5); //release: Don't care
        public const bool new_random_set_every_time = true; //release: Don't care
        
        //Bug fixes

        public const bool fix_psi_skill_overflow_bug = true; //release: true
        public const byte psi_skill_cap = 169; //release: 169 //at 100(+5) stats can only be increased via psi lab (not through combat), above 169 game displays graphical errors, above 255 the stat wraps around to 0.

        //
    }

    public static class GlobalVariables 
    {
        public const int sleep_time_before_first_file_open = 1000, sleep_time_between_file_open_tries = 100; //[ms]

        public static int[] stat_initial_min = new int[]  { 50,  40, 25,  10,  30,  40,  50, 20 };
        public static int[] stat_initial_max = new int[]  { 60,  70, 40,  60,  60,  70,  80, 40 };
        public static int[] stat_cap_min     = new int[]  { 80, 100, 60, 100, 100, 120, 120, 70 };
        public static int[] stat_cap_max     = new int[]  { 81, 101, 61, 100, 105, 125, 125, 71 };

        public const double average_of_spreads = 63; //avg of (maxcap - initmin)

        public static string[] classes = new string[]                           { "Assault", "Sniper", "Shock", "Medic", "Heavy", "Support" };

        public static string[] class_order_in_flight_and_in_menu = new string[] { "Shock", "Sniper", "Assault", "Heavy", "Support", "Medic" }; //make sure these names fit the above!

        public const string always_suggest_to_crappy_soldiers = "Shock"; //if "", a class is suggested to every soldier

        /////////////////// ONLY changes which class is chosen, NOT the actual levels of the soldier, the overall level distribution will naturally be affected by this

        public static double[] class_distribution_calibration_values_start = new double[] // the lower; the harder it is to qualify for the class, the higher; the easier. Used to calibrate the percentage likelyhood of classes. 0 = no change                            
                                                                              
                                {        //       Target value
                                +   0.5395, //Assault    25%
                                +   0.3570, //Sniper     30%      
                                +   0.4990, //Shock      25% 
                                -   3.4160, //Medic       5%
                                -   0.7090, //Heavy      10%  
                                +   2.8005  //Support     5%
                                };

        public static double[] class_distribution_calibration_values_middle = new double[]
                                    
                                {      
                                +   0.0, //Assault    
                                +   2.0, //Sniper      
                                +   0.0, //Shock      
                                +   0.0, //Medic       
                                +   0.0, //Heavy     
                                -   2.0  //Support   
                                };

        public static double[] class_distribution_calibration_values_end = new double[]
                                    
                                {      
                                +   0.0, //Assault    
                                +   2.0, //Sniper      
                                +   0.0, //Shock      
                                +   1.0, //Medic       
                                +   1.0, //Heavy     
                                -   1.5  //Support   
                                };

        public const double class_distribution_calibration_values_middle_point = 0.75;  //used to determine limit for when the factors apply
        public const double class_distribution_calibration_values_end_point = 1;

        ///////////////////

        public static double[,] class_values = new double[,] // only ratios between numbers matter, values below 1 would probably result in undesired behavior. Difference in sums of class values are taken into acc
                      
                                { //  t   s   h  (B)  R   F   T   s  PAS PDS   //upper-case: Primary stat lower-case: Secondary stat
	                                {  5,  5,  5,3.7, 10, 10,  1,  5,  0,3.8}, //Assault 
	                                {  4,  3,  3,  3,  7, 12,1.1,  4,  0,3.1}, //Sniper  
	                                {  8,  7,  5,  4, 12,  8,  1,  3,  0,4.1}, //Shock  
	                                {  8,  5,  2,  8,  4,  2,0.8,  2,  0,  2}, //Medic
	                                {  4,  5,  3,  7,  3, 12,1.5, 11,  0,  6}, //Heavy    
	                                {  5,  6,  4,  4,  4,6.5,2.6,  7,  0,3.3}  //Support  
                                };

        public static double[,] class_min_requirements = new double[,] //sets the limit for when the soldiers stats are too low as to be outright sacked for the specific class, the higher the higher the stat has to be (min required stat = init stat min + value * class_min_requirements_calibration_value * (init stat max - init stat min) * class value expressed as x time the avg number for the class)
                      
                                { //  t    s    h   (B)   R    F    T    s     //upper-case: Primary stat lower-case: Secondary stat
	                                {0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00}, //Assault 
	                                {0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00}, //Sniper  
	                                {0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00}, //Shock  
	                                {0.00,0.00,0.00,0.75,0.00,0.00,0.00,0.00}, //Medic
	                                {0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.50}, //Heavy    
	                                {0.33,0.33,0.33,0.33,0.33,0.33,0.33,0.33}  //Support  
                                };

        public const double class_min_requirements_factor = 0;//0.75; //if 1 class min requirements system = off, else if below min requirement multiply score by factor (should be within [0;1])

        private static int _classes_longest_length;

        public static int classes_longest_length
        {
            get
            {
                if (_classes_longest_length == 0)
                {
                    foreach (string class_name in classes)
                    {
                        if (class_name.Length > _classes_longest_length)
                        {
                            _classes_longest_length = class_name.Length;
                        }
                    }
                }

                return _classes_longest_length;
            }
        }

        public const double level_curve = 2.5;//2.4875; //the slope of leveling, the higher the faster the levels at low level is, and the slower at high levels. If below 1 the reversed, If 1 = linear, if 0 or lower NOT supported i.e. crash or unknown behaviour. 

        public const double min_level_calibration_value = 11/*10.975*/, max_level_calibration_value = 60; //used to determine how good a soldier has to be to qualify for the highest/lowest possible levels

        public const int max_starting_level = 5; //failsafe

        public const double move_up_to_min_level_calibration_value = -1.77; //if 0 then no soldiers are moved up, if above 0.. ?!?//for at gøre så færre soldater bliver anset som for 'for dårlige' til en class (flytter lavere levels op til level 1)

        public static double[] level_limits_calibration = new double[] // if all indexes are equal to index number + 1 then no change // OBS MUST HAVE ASCENDING ORDER! ALL THE WAY! // Should be used with caution and only after all other calibration values are optimized
                                /*{      //Move up to level:
                                    1.000, // Min. level
                                    1.950, // 1-2  
                                    2.595, // 2-3  // if this is set to 3.2 and the value above is set to 1.7 the levels between 2 and 3 will be stretched to between 1.7 and 3.2
                                    3.885, // 3-4  
                                    5.440, // 4-5
                                    6.300, // 5-6
                                    7.100, // 6-7
                                    8.000, // 7-8
                                    9.000, // 8-9
                                   10.000  // 9-10
                                };*/
                                {      //Move up to level:
                                    1.000, // Min. level
                                    2.130, // 1-2  
                                    2.670, // 2-3  // if this is set to 3.2 and the value above is set to 1.7 the levels between 2 and 3 will be stretched to between 1.7 and 3.2
                                    3.978, // 3-4  
                                    5.505, // 4-5
                                    6.300, // 5-6
                                    7.100, // 6-7
                                    8.000, // 7-8
                                    9.000, // 8-9
                                   10.000  // 9-10
                                };

        public const double class_requirements_values_magnitude       = 1 / 20.0; // the magnitude the avg value assumes (of max)
        public const double class_requirements_values_magnitude_min   = 1       ;
        public const double class_requirements_values_magnitude_max   = 3       ;
        public const double class_requirements_values_magnitude_limit = 2 / 3.0 ; // 33.7% is the average equalized stat at max init, i.e. *2 at 1/3 and *3 at 2/3 or higher //1 equals min of stat cap //strange things begins to happen when stats cap out.. for now the workaround is to keep this value low

        public static double[] stamina_regen_importance_factors = new double[5] //{1,1,1,1,1}; //how much stamina regen alters stamina
                                        {     //Possible values of energy (STA) regen per turn
                                        0.85, //16
                                        0.95, //17
                                        1.00, //18
                                        1.05, //19
                                        1.15  //20
                                        };
    }

    public class SavedGame 
    {
        public string name;
        public string file_path; 
        public Collection<Soldier> soldiers;    

        private byte[] bytes_in_file; 

        public enum UpdateFile { Yes, No }

        public bool is_geoscape_save;

        private List<double> soldier_levels = new List<double>(); //used solely as temporary storage for the depentable statistical variables (mean, variance and median), not intended to be called outside of these. This can easily be modfied, which would optimize, but i chose not to mess with old reliable code.

        private double temp;

        public double mean
        {
            get
            {
                if (soldiers.Count == 0) return 0;

                soldier_levels.Clear();
                foreach (Soldier soldier in soldiers)
                {
                    if (soldier.alive)
                    {
                        if (soldier.Level_floating < 1) soldier_levels.Add(1);
                        else soldier_levels.Add(soldier.Level_floating);
                    }
                }

                return soldier_levels.Average();

            }
        }

        public double variance //using sample standard deviation, NOT Population standard deviation. This version is better for small sample sizes.
        {
            get
            {
                if (soldiers.Count == 0) return 0;
                else
                {
                    soldier_levels.Clear();
                    foreach (Soldier soldier in soldiers)
                    {
                        if (soldier.alive)
                        {
                            if (soldier.Level_floating < 1) soldier_levels.Add(1);
                            else soldier_levels.Add(soldier.Level_floating);
                        }
                    }

                    temp = 0;

                    foreach (double value in soldier_levels)
                    {
                        temp += Math.Pow(value - soldier_levels.Average(),2); // i assume the JIT catches this huge optimisation sinkhole
                    }

                    return temp / (soldier_levels.Count - 1); // the minus one is known as "Bessel's correction". No sqrt since i'm returning the variance.. i.e. not the STD
                }
            }
        }
         
        public double median
        {
            get
            {
                if (soldiers.Count == 0) return 0;
                else
                {
                    soldier_levels.Clear();
                    foreach (Soldier soldier in soldiers)
                    {
                        if (soldier.alive)
                        {
                            if (soldier.Level_floating < 1) soldier_levels.Add(1);
                            else soldier_levels.Add(soldier.Level_floating);
                        }
                    }

                    soldier_levels.Sort(); // sorts in ascending order

                    if ((soldier_levels.Count & 0x01) == 1) //odd
                    {
                        return soldier_levels[((soldier_levels.Count + 1) / 2) - 1]; // -1 since the index starts at zero and not one
                    }
                    else //even
                    {
                        return (soldier_levels[(soldier_levels.Count) / 2] + soldier_levels[(soldier_levels.Count / 2) - 1]) / 2; //average of the 2 middle values
                    }
                }
            }
        }

        public SavedGame(string path, UpdateFile update_file)
        {
            this.file_path = path;
            this.name = this.file_path.Split('\\').ElementAt(this.file_path.Split('\\').Length - 1 - 1);

            soldiers = new Collection<Soldier>();

            update(update_file);
        }

        public void update(UpdateFile update_file)
        {
            if (!geoscape_save()) return; //do NOT modify mission saves, no gain and quite bug prone, atleast more so than the countermeasure

            Soldier[] previous_soldiers = new Soldier[soldiers.Count];

            soldiers.CopyTo(previous_soldiers, 0);

            ////////////////////////////

            soldiers.Clear();

            if (RunConditions.test_active == false)
            {
                byte[] temp = ReadFile(file_path);

                bytes_in_file = new byte[temp.Length];

                temp.CopyTo(bytes_in_file, 0);

                //bytes_in_file = ReadFile(file_path);
            }
            
            parse_file_and_build_soldier_collection();

            if (soldiers.Count != 0)
            {
                check_for_soldier_improvements(previous_soldiers);

                sort_soldiers();
                
                if (update_file.Equals(UpdateFile.Yes))
                {
                    foreach (Soldier soldier in soldiers) soldier.update_file_entry(file_path);
                }
            }
        }

        private bool geoscape_save()
        {
            if (RunConditions.test_active == false)
            {
                int sensitivity = 5;

                string path = file_path.Remove(file_path.Length - 11) + "obpos.dat";

                if (File.Exists(path))
                {
                    DateTime soldier_dat = File.GetLastWriteTime(file_path);
                    DateTime obpos_dat = File.GetLastWriteTime(path);

                    if (soldier_dat.AddSeconds(-sensitivity) < obpos_dat && soldier_dat.AddSeconds(sensitivity) > obpos_dat) //if close in write time assume that they are from the same save
                    {
                        return is_geoscape_save = false; //mission save
                    }
                }
            }
            return is_geoscape_save = true;
        }

        private byte[] ReadFile(string filePath)
        {
            byte[] buffer;

            FileStream fileStream;

            int length;

            do
            {
                try
                {
                    fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);

                    length = Convert.ToInt32(fileStream.Length);  // get file length

                    if (length != 17000) throw new Exception("File length not 17000"); 

                    buffer = new byte[length];            // create buffer

                    fileStream.Read(buffer, 0, length);

                    fileStream.Close();

                    return buffer;
                }
                catch
                {
                    System.Threading.Thread.Sleep(GlobalVariables.sleep_time_between_file_open_tries); //wait and try again
                }

            } while (true); //continue forever
        }

        private void parse_file_and_build_soldier_collection()
        {
            if (RunConditions.test_active)
            {
                int[] stats_improved = new int[8], stats_initial = new int[8];

                int psi_skill = 0, psi_strength = 0;

                if (RunConditions.if_true_large_sample_if_false_just_the_one_soldier)
                {
                    Random rand;

                    if (RunConditions.new_random_set_every_time) rand = new Random();
                    else                                         rand = new Random(0);

                    for (int i = 0; i < RunConditions.number_of_soldiers_to_test; i++)
                    {
                        for (int j = 0; j < 8; j++)
                        {
                            stats_initial[j] = rand.Next(GlobalVariables.stat_initial_min[j], GlobalVariables.stat_initial_max[j] + 1);
                        }

                        byte[] entry = new byte[68];

                        entry[42] = Convert.ToByte(stats_initial[0]);
                        entry[44] = Convert.ToByte(stats_initial[1]);
                        entry[43] = Convert.ToByte(stats_initial[2]);
                        entry[52] = Convert.ToByte(11 - (stats_initial[3] / 10.0));
                        entry[45] = Convert.ToByte(stats_initial[4]);
                        entry[47] = Convert.ToByte(stats_initial[5]);
                        entry[48] = Convert.ToByte(stats_initial[6]);
                        entry[46] = Convert.ToByte(stats_initial[7]);

                        entry[53] = Convert.ToByte(stats_improved[0]);
                        entry[55] = Convert.ToByte(stats_improved[1]);
                        entry[54] = Convert.ToByte(stats_improved[2]);
                        entry[61] = Convert.ToByte(stats_improved[3] / 10.0);
                        entry[56] = Convert.ToByte(stats_improved[4]);
                        entry[58] = Convert.ToByte(stats_improved[5]);
                        entry[59] = Convert.ToByte(stats_improved[6]);
                        entry[57] = Convert.ToByte(stats_improved[7]);
                        
                        soldiers.Add(new Soldier(0, entry));
                    }
                }
                else
                {
                    switch (RunConditions.soldier_type)
                    {
                        case 0:
                        case 2:
                        case 4:
                            if (RunConditions.soldier_type == 2)
                            {
                                if (RunConditions.if_true_psi_skill_combat_cap_if_false_lab_cap) psi_skill = 100;
                                else psi_skill = RunConditions.psi_skill_cap;
                                psi_strength = 0;
                            }
                            GlobalVariables.stat_initial_min.CopyTo(stats_initial, 0);
                            break;
                        case 1:
                        case 3:
                            if (RunConditions.soldier_type == 3)
                            {
                                if (RunConditions.if_true_psi_skill_combat_cap_if_false_lab_cap) psi_skill = 101;
                                else psi_skill = RunConditions.psi_skill_cap;
                                psi_strength = 100;
                            }
                            GlobalVariables.stat_initial_max.CopyTo(stats_initial, 0);
                            break;
                    }

                    for (int i = 0; i < 8; i++)
                    {
                        switch (RunConditions.soldier_type)
                        {
                            case 0:
                            case 1:
                                stats_improved[i] = 0;
                                break;
                            case 2:
                                stats_improved[i] = GlobalVariables.stat_cap_min[i] - stats_initial[i];
                                break;
                            case 3:
                                stats_improved[i] = GlobalVariables.stat_cap_max[i] - stats_initial[i];
                                break;
                            case 4:
                                stats_improved[i] = GlobalVariables.stat_initial_max[i] - stats_initial[i];
                                break;
                        }
                    }

                    byte[] entry = new byte[68];

                    entry[42] = Convert.ToByte(stats_initial[0]);
                    entry[44] = Convert.ToByte(stats_initial[1]);
                    entry[43] = Convert.ToByte(stats_initial[2]);
                    entry[52] = Convert.ToByte(11 - (stats_initial[3] / 10.0));
                    entry[45] = Convert.ToByte(stats_initial[4]);
                    entry[47] = Convert.ToByte(stats_initial[5]);
                    entry[48] = Convert.ToByte(stats_initial[6]);
                    entry[46] = Convert.ToByte(stats_initial[7]);

                    entry[53] = Convert.ToByte(stats_improved[0]);
                    entry[55] = Convert.ToByte(stats_improved[1]);
                    entry[54] = Convert.ToByte(stats_improved[2]);
                    entry[61] = Convert.ToByte(stats_improved[3] / 10.0);
                    entry[56] = Convert.ToByte(stats_improved[4]);
                    entry[58] = Convert.ToByte(stats_improved[5]);
                    entry[59] = Convert.ToByte(stats_improved[6]);
                    entry[57] = Convert.ToByte(stats_improved[7]);

                    entry[50] = Convert.ToByte(psi_strength);
                    entry[51] = Convert.ToByte(psi_skill);

                    soldiers.Add(new Soldier(0, entry));
                }
            }
            else
            {
                byte[] entry = new byte[68];

                for (int entry_index = 0; (entry_index + entry.Length) < bytes_in_file.Length; entry_index += entry.Length)
                {
                    for (int i = 0; i < entry.Length; i++)
                    {
                        entry[i] = bytes_in_file[entry_index + i];
                    }
                    
                        bool data_in_entry = false;

                        //dos version 
                    
                        for (int i = 0; i < entry.Length; i++)
                        {
                            if (!entry[i].Equals(0x00) && !entry[i].Equals(0xFF))
                            {
                                data_in_entry = true;
                                break;
                            }
                        }
                        
                        //                       

                    if (data_in_entry)
                    {
                        Soldier temp;
                        
                        try
                        {
                            temp = new Soldier(entry_index, entry);

                            soldiers.Add(temp);
                        }
                        catch
                        {
                            //soldier entry not the correct format, ignore
                        }
                    }
                    else
                    {
                        return;
                    }
                }
            }
        }

        private void check_for_soldier_improvements(Soldier[] previous_soldiers)
        {
            foreach (Soldier soldier in soldiers) foreach (Soldier previous_soldier in previous_soldiers) // could easily be optimised, but i doubt it would make much diff.
            {
                if (soldier.Equals(previous_soldier))
                {
                    if (soldier.Level > previous_soldier.Level) soldier.level_up = true;
                    else                                        soldier.level_up = false;

                    if (soldier.chosen_class_number != previous_soldier.chosen_class_number) soldier.class_switch = true;
                    else                                                                     soldier.class_switch = false;
                }
            }
        }

        private void sort_soldiers()
        {
            int max_level, min_level;
            Collection<int> entry_indexes = new Collection<int>();

            { // could easily be improved but should work well enough
                int[] class_levels = new int[soldiers.Count];

                int i = 0;

                foreach (Soldier soldier in soldiers)
                {
                    if (soldier.alive && soldier.in_transit == false)
                    {
                        class_levels[i++] = soldier.Level;
                        entry_indexes.Add(soldier.entry_index);
                    }
                }

                max_level = class_levels.Max();
                min_level = class_levels.Min();
            }
            
            ////////

            int entry_index_counter = 0;

            for (int class_number = 0; class_number < GlobalVariables.classes.Length; class_number++) for (int level = min_level; level <= max_level; level++) foreach (Soldier soldier in soldiers)
            {
                if (soldier.chosen_class.Equals(GlobalVariables.class_order_in_flight_and_in_menu[class_number]) && 
                    soldier.Level == level                                                            && 
                    soldier.alive                                                                     &&
                    soldier.in_transit == false                                                         )
                {
                    soldier.entry_index = entry_indexes.ElementAt(entry_index_counter++);
                }
            }
        }   
    }

    public class Soldier // pretty sure that this warning is irrelevant 
    {
        private static int space_used_for_seperator_name_class = 1 + 1, space_used_for_class = GlobalVariables.classes_longest_length, space_used_for_seperator_level_name = 1, space_used_for_level = 1;

        public char normal_seperator_marker = '-', did_not_qualify_for_a_class_marker = 'x', class_switch_marker = '>', level_up_marker = '^', special_name_marker = ':';

        public static char[] user_input_indicator = new char[] {'|','='};

        public bool[] recieved_user_input_indicators = new bool[user_input_indicator.Length];

        public byte[] name = new byte[21 - space_used_for_seperator_name_class - space_used_for_class - space_used_for_seperator_level_name - space_used_for_level];
        
        /////////////////////////////

        public int entry_index;

        private byte[] entry = new byte[68];

        /////////////////////////////

        //public string new_name_string;

        /////////////////////////////

        public Soldier(int entry_index, byte[] entry) //real
        {
            this.entry_index = entry_index;

            entry.CopyTo(this.entry,0);

            /////////

            check_if_dead();

            check_if_in_transit();

            parse_stats();

            parse_name();

            /////////

            calculate_class_scores();

            determine_class();
            
            determine_level();
        }

        public override bool Equals(System.Object obj) //should work since there are 2068480171296 combinations of new soldiers stats (11 * 31 * 16 * 6 * 31 * 31 * 31 * 21 * 101), so pretty low chance of hitting a false positive. EXCEPT if it's the same soldier from different saves! This would be a problem if he's lvled differently!... however the whole point of this is to recognise the soldier from a previous save after he's gained extra stats..
        {
            // If parameter is null return false.
            if (obj == null)
            {
                return false;
            }

            // If parameter cannot be cast to Point return false.
            Soldier p = obj as Soldier;
            if ((System.Object)p == null)
            {
                return false;
            }

            // Return true if the fields match:

            for (int i = 0; i < 8; i++) 
            {
                if (stats_initial_original[i] != p.stats_initial_original[i])
                {
                    return false;
                }
            }

            if (psi_strength != p.psi_strength) return false;

            return true;
        }

        /////////////////////////////

        public bool level_up = false;

        public bool class_switch = false;

        public int energy_regen;

        public bool psi_locked = true;

        public int psi_skill, psi_strength, psi_attack_strength, psi_defense_strength;

        public double psi_def_same_format_as_equalized_stats, psi_atk_same_format_as_equalized_stats;

        public int UserDeterminedClass = 0; // 0: false, 1: [0], 2: [1] user_input_indicator

        public bool alive = true, in_transit = false;

        public string chosen_class;

        public int _chosen_class_number;

        public int chosen_class_number
        {
            set 
            {
                _chosen_class_number = value;
                chosen_class = GlobalVariables.classes[value];
            }
            get
            {
                return _chosen_class_number;
            }
        }

        public int chosen_class_number_by_program, chosen_class_number_by_user;

        public double Level_floating;

        public int Level;

        public double[] class_scores_used_for_class, class_score_used_for_class_altered_with_requirement_values, class_scores_used_for_level;

        public int[] stats_original, stats_initial_original, stats_beyond_cap;

        public double[] stats_improved_original, stats_improved_altered, stats_altered/*, stats_initial_altered*/;

        //public double[] stats_improved_up_to_initial_stat_cap, stats_improved_beyond_initial_stat_cap;

        //public double[] stats_improved_altered_up_to_initial_stat_cap, stats_improved_altered_beyond_initial_stat_cap;

        public double[] stats_equalized;

        private void calculate_class_scores()
        {
            ///////////////////////////////////

            int number_of_stats;

            if (psi_locked) number_of_stats = 8;
            else            number_of_stats = 8 + 2;

            double[,] class_score_offsets = new double[GlobalVariables.classes.Length, number_of_stats], class_score_weightings = new double[GlobalVariables.classes.Length, number_of_stats]; //score i forskellige stats valgt i forhold til andre score i samme klasse og score i andre klasser

            double[] class_value_sums = new double[GlobalVariables.classes.Length];

            for (int i = 0; i < GlobalVariables.classes.Length; i++) for (int j = 0; j < number_of_stats; j++)
            {
                class_score_offsets[i, j] = GlobalVariables.class_values[i, j];             

                /////

                class_value_sums[i] += class_score_offsets[i, j];
            }

            for (int i = 0; i < GlobalVariables.classes.Length; i++) for (int j = 0; j < number_of_stats; j++)
            {
                class_score_offsets[i, j] /= class_value_sums[i] / Convert.ToDouble(number_of_stats); //div by avg score, i.e above avg equals greater than 1, below equals lower than 1

                class_score_offsets[i, j] *= GlobalVariables.average_of_spreads;
            }

            //////////////////// 
            
            double value = GlobalVariables.average_of_spreads * 8 * GlobalVariables.class_requirements_values_magnitude_limit;

            if (stats_equalized.Sum() < value) 
            {
                double actual_requirement_factor = GlobalVariables.class_requirements_values_magnitude_min + ((GlobalVariables.class_requirements_values_magnitude_max - GlobalVariables.class_requirements_values_magnitude_min) * (stats_equalized.Sum() / value));

                for (int i = 0; i < GlobalVariables.classes.Length; i++)
                {
                    for (int j = 0; j < number_of_stats; j++)
                    {
                        class_score_offsets[i, j] *= GlobalVariables.class_requirements_values_magnitude * actual_requirement_factor;
                    }
                }
            }
            else
            {
                for (int i = 0; i < GlobalVariables.classes.Length; i++) for (int j = 0; j < number_of_stats; j++)
                    {
                        class_score_offsets[i, j] *= GlobalVariables.class_requirements_values_magnitude * GlobalVariables.class_requirements_values_magnitude_max;
                    }
            }

            ///////////////////////////////////

            for (int i = 0; i < GlobalVariables.classes.Length; i++) for (int j = 0; j < number_of_stats; j++)
            {
                class_score_weightings[i, j] = GlobalVariables.class_values[i, j];                             
            }

            ///////////////////////////////////

            double[,] specific_class_score_offsets = new double[GlobalVariables.classes.Length, number_of_stats];

            for (int i = 0; i < GlobalVariables.classes.Length; i++) for (int j = 0; j < number_of_stats; j++)
            {
                     if (j == 8) specific_class_score_offsets[i, j] = psi_atk_same_format_as_equalized_stats - class_score_offsets[i, j];
                else if (j == 9) specific_class_score_offsets[i, j] = psi_def_same_format_as_equalized_stats - class_score_offsets[i, j];
                else             specific_class_score_offsets[i, j] = stats_equalized[j] - class_score_offsets[i, j];
            }

            ///////////////////////////////////

            class_scores_used_for_class = new double[GlobalVariables.classes.Length];

            for (int i = 0; i < GlobalVariables.classes.Length; i++)
            {
                double class_weighting_sum = 0;

                for (int j = 0; j < number_of_stats; j++)
                {
                    class_weighting_sum += class_score_weightings[i, j];

                    class_scores_used_for_class[i]     += specific_class_score_offsets[i, j] * class_score_weightings[i, j];
                }

                class_scores_used_for_class[i] /= class_weighting_sum;
            }

            ///////////////////////////////////

            class_scores_used_for_level = new double[GlobalVariables.classes.Length];
            
            for (int i = 0; i < GlobalVariables.classes.Length; i++)
            {
                double class_weighting_sum = 0;

                for (int j = 0; j < number_of_stats; j++)
                {
                    class_weighting_sum += class_score_weightings[i, j];

                         if (j == 8) class_scores_used_for_level[i] += psi_atk_same_format_as_equalized_stats * class_score_weightings[i, j];
                    else if (j == 9) class_scores_used_for_level[i] += psi_def_same_format_as_equalized_stats * class_score_weightings[i, j];
                    else             class_scores_used_for_level[i] += stats_equalized[j] * class_score_weightings[i, j];
                }

                class_scores_used_for_level[i] /= class_weighting_sum;

                ////

                class_scores_used_for_level[i] -= GlobalVariables.min_level_calibration_value;
            }

            /////////////////////////////////// used for plotting level distribution
                 
            /*double[] test = new double[1000];
            double[] test2 = new double[1000];

            for (double i = 0; i < test.Length; i++)
            {
                test[Convert.ToInt32(i)] = calculate_level(i / 10.0, false);
                test2[Convert.ToInt32(i)] = calculate_level(i / 10.0, true);
            }*/
            
        }
        
        private void determine_class()
        {
            class_score_used_for_class_altered_with_requirement_values = new double[GlobalVariables.classes.Length];

            double middle_point = GlobalVariables.average_of_spreads * 8 * GlobalVariables.class_distribution_calibration_values_middle_point;
            double end_point    = GlobalVariables.average_of_spreads * 8 * GlobalVariables.class_distribution_calibration_values_end_point;

            if (stats_equalized.Sum() < middle_point)
            {
                for (int i = 0; i < GlobalVariables.classes.Length; i++)
                {
                    class_score_used_for_class_altered_with_requirement_values[i] = class_scores_used_for_class[i] + GlobalVariables.class_distribution_calibration_values_start[i] + ((GlobalVariables.class_distribution_calibration_values_middle[i] - GlobalVariables.class_distribution_calibration_values_start[i]) * (stats_equalized.Sum() / middle_point));
                }
            }
            else if (stats_equalized.Sum() < end_point)
            {
                for (int i = 0; i < GlobalVariables.classes.Length; i++)
                {
                    class_score_used_for_class_altered_with_requirement_values[i] = class_scores_used_for_class[i] + GlobalVariables.class_distribution_calibration_values_middle[i] + ((GlobalVariables.class_distribution_calibration_values_end[i] - GlobalVariables.class_distribution_calibration_values_middle[i]) * ((stats_equalized.Sum() - middle_point) / (end_point - middle_point)));
                }
            }
            else
            {
                for (int i = 0; i < GlobalVariables.classes.Length; i++)
                {
                    class_score_used_for_class_altered_with_requirement_values[i] = class_scores_used_for_class[i] + GlobalVariables.class_distribution_calibration_values_end[i];
                }
            }



            if (GlobalVariables.class_min_requirements_factor != 1)
            {
                for (int i = 0; i < class_scores_used_for_class.Length; i++)
                {
                    if (class_score_used_for_class_altered_with_requirement_values[i] < 0) class_score_used_for_class_altered_with_requirement_values[i] = 0;
                    else
                    {
                        for (int j = 0; j < 8; j++)
                        {
                            if (stats_original[j] < (GlobalVariables.stat_initial_min[j] + (GlobalVariables.stat_initial_max[j] - GlobalVariables.stat_initial_min[j]) * GlobalVariables.class_min_requirements[i, j]))
                            {
                                class_score_used_for_class_altered_with_requirement_values[i] *= GlobalVariables.class_min_requirements_factor;
                                break;
                            }
                        }
                    }
                }
            }
            
            

            Collection<int> indexs_of_max = new Collection<int>();

            for (int i = 0; i < class_scores_used_for_class.Length; i++)
            {
                if (class_score_used_for_class_altered_with_requirement_values[i] == class_score_used_for_class_altered_with_requirement_values.Max())
                {
                    indexs_of_max.Add(i);
                }
            }





            double[] class_score_used_for_class_altered_with_requirement_values_sorted = new double[class_score_used_for_class_altered_with_requirement_values.Length];

            class_score_used_for_class_altered_with_requirement_values.CopyTo(class_score_used_for_class_altered_with_requirement_values_sorted, 0);

            Array.Sort(class_score_used_for_class_altered_with_requirement_values_sorted);

            Array.Reverse(class_score_used_for_class_altered_with_requirement_values_sorted);






            if (indexs_of_max.Count == 1) // the if is purely cosmetic... but may improve performance slightly
            {
                chosen_class_number_by_program = indexs_of_max.ElementAt(0);
            }
            else //in case of several pick the one with the highest lvl score
            {
                chosen_class_number_by_program = indexs_of_max.ElementAt(0);

                for (int i = 0 + 1; i < indexs_of_max.Count; i++)
                {
                    if (class_scores_used_for_level[indexs_of_max.ElementAt(i)] > class_scores_used_for_level[chosen_class_number_by_program])
                    {
                        chosen_class_number_by_program = indexs_of_max.ElementAt(i);
                    }
                }

                //chosen_class_number_by_program = indexs_of_max.ElementAt((new Random()).Next(0, indexs_of_max.Count - 1)); // in case several classes had same score then pick at random
            }




            if (class_scores_used_for_level[chosen_class_number_by_program] < GlobalVariables.move_up_to_min_level_calibration_value || class_score_used_for_class_altered_with_requirement_values.Max() == 0)
            {
                Level = -1; //negative value = did not qualify for a class

                if (GlobalVariables.always_suggest_to_crappy_soldiers != "")
                {
                    for (int i = 0; i < GlobalVariables.classes.Length; i++)
                    {
                        if (GlobalVariables.classes[i].Equals(GlobalVariables.always_suggest_to_crappy_soldiers))
                        {
                            chosen_class_number_by_program = i;
                            break;
                        }
                    }
                }
            }




            chosen_class_number_by_user = search_for_and_parse_user_input();

            if (chosen_class_number_by_user < -1)
            {
                     if (recieved_user_input_indicators[0]) UserDeterminedClass = 1;
                else if (recieved_user_input_indicators[1]) UserDeterminedClass = 2;
        
                double temp = class_score_used_for_class_altered_with_requirement_values_sorted.ElementAt((chosen_class_number_by_user * (-1))-1);

                for(int i = 0; i<class_score_used_for_class_altered_with_requirement_values.Length;i++)
                {
                    if (class_score_used_for_class_altered_with_requirement_values[i] == temp)
                    {
                        chosen_class_number = i;
                        break;
                    }
                }
            }
            else if (chosen_class_number_by_user != -1 && !(recieved_user_input_indicators[1] && chosen_class_number_by_user == chosen_class_number_by_program ))
            {
                     if (recieved_user_input_indicators[0]) UserDeterminedClass = 1;
                else if (recieved_user_input_indicators[1]) UserDeterminedClass = 2;

                chosen_class_number = chosen_class_number_by_user;
            }
            else
            {
                UserDeterminedClass = 0;
                chosen_class_number = chosen_class_number_by_program;
            }


            /*
            if (chosen_class_number == 3 && stats_original[3] != 60)
            {
            }
            */

        }

        private int search_for_and_parse_user_input() //returns -1 if no coherent user input found, else returns the chosen_class_number
        {
            string field = "";
            int switch_to_next = 0;


            for (int i = 0; i < 21 && entry[i + 16] != 0; i++) //for some strange reason names of new soldiers consists of a name then an end marker and then traces of old names?!?
            {
                 field += Convert.ToChar(entry[i + 16]);
            }

            if ((recieved_user_input_indicators[0] = field.Contains(user_input_indicator[0])) || (recieved_user_input_indicators[1] = field.Contains(user_input_indicator[1])))
            {
                field = field.ToLower();

                     if (field.Contains("2nd"))
                {
                    switch_to_next = 2;
                }
                else if (field.Contains("3rd"))
                {
                    switch_to_next = 3;
                }
                else if (field.Contains("4th"))
                {
                    switch_to_next = 4;
                }
                else if (field.Contains("5th"))
                {
                    switch_to_next = 5;
                }
                else if (field.Contains("6th"))
                {
                    switch_to_next = 6;
                }
                else if (field.Contains("7th"))
                {
                    switch_to_next = 7;
                }
                else if (field.Contains("8th"))
                {
                    switch_to_next = 8;
                }
                else if (field.Contains("9th"))
                {
                    switch_to_next = 9;
                }
                     
                if (switch_to_next > 0 && switch_to_next <= (GlobalVariables.classes.Length - 1 +1))
                {
                    return (-1 * switch_to_next);
                }

                for (int i = 0; i < GlobalVariables.classes.Length; i++)
                {
                    if (field.Contains(GlobalVariables.classes[i].ToLower()))
                    {
                        return i;
                    }
                }
            }

            return -1;
        }

        private void determine_level()
        {
            if (Level != -1) //i.e. didn't qualify for a class
            {
                double score;

                if (class_scores_used_for_level[chosen_class_number] < 0) score = 0;
                else                                                      score = class_scores_used_for_level[chosen_class_number];

                Level_floating = calculate_level(score, true);

                Level = Convert.ToInt32(Math.Floor(Level_floating)); // Original, before statistics: Level = Convert.ToInt32(Math.Floor(calculate_level(score,true)));
                
                if (Level >= Convert.ToInt32(Math.Pow(10, space_used_for_level)))
                {
                    Level = Convert.ToInt32(Math.Pow(10, space_used_for_level)) - 1; //if level is a number higher than the amount of chars will allow, set it to max, i.e. if only one char available and 10 or higher set to 9 (failsafe)
                }

                if (stats_improved_original.Max() == 0 && Level > GlobalVariables.max_starting_level) Level = GlobalVariables.max_starting_level;  //failsafe 
            }
        }

        private double alter_level_limits(double score)
        {
            for (int level = 1; level < GlobalVariables.level_limits_calibration.Length; level++)
            {
                if (convert_level_back_to_score(level) <= score && score <= convert_level_back_to_score(level + 1))
                {
                    // normaliser fra mellem convert_level_back_to_score(level)                                           og convert_level_back_to_score(level+1)          til
                    //                mellem convert_level_back_to_score(GlobalVariables.move_up_a_level_limits[level-1]) og convert_level_back_to_score(GlobalVariables.move_up_a_level_limits[level])

                    return convert_level_back_to_score(GlobalVariables.level_limits_calibration[level - 1]) + normalize(score, convert_level_back_to_score(level), convert_level_back_to_score(level + 1), 1) * (convert_level_back_to_score(GlobalVariables.level_limits_calibration[level]) - convert_level_back_to_score(GlobalVariables.level_limits_calibration[level - 1]));
                }
            }

            return score;
        }

        public double calculate_level(double score, bool alter_level_limits_switch)
        {
            if (score < 0) // mirror around 0
            {
                return (-1) * calculate_level(score * (-1), alter_level_limits_switch);
            }

            if (alter_level_limits_switch) score = alter_level_limits(score);

            return 1 + Math.Pow(score * (Math.Pow(Convert.ToInt32(Math.Pow(10, space_used_for_level)) - 1, GlobalVariables.level_curve) / GlobalVariables.max_level_calibration_value), 1 / GlobalVariables.level_curve);
        }

        private double convert_level_back_to_score(double level)
        {
            return Math.Pow((level - 1), GlobalVariables.level_curve) * GlobalVariables.max_level_calibration_value * Math.Pow((Math.Pow(10, space_used_for_level) - 1), (-GlobalVariables.level_curve));
        }

        /////////////////////////////

        private void check_if_dead()
        {
            if (entry[0].Equals(0xFF) && entry[1].Equals(0xFF))
            {
                alive = false;
            }
        }

        private void check_if_in_transit()
        {
            if (entry[2].Equals(0xFF))
            {
                in_transit = true;
            }
        }

        private void parse_stats()
        {
            stats_initial_original  = new int[]    {entry[42],entry[44],entry[43],(110 - (entry[52] * 10)),entry[45],entry[47],entry[48],entry[46]};

            stats_improved_original = new double[] {entry[53],entry[55],entry[54],        entry[61] * 10  ,entry[56],entry[58],entry[59],entry[57]};

            parse_psi_stats();

            parse_energy_regen();

            ///////////////////////

            //stats_initial_altered = new double[8];

            //stats_initial_original.CopyTo(stats_initial_altered, 0);

            stats_improved_altered = new double[8];

            stats_improved_original.CopyTo(stats_improved_altered, 0);

            stats_original = new int[8];
            
            stats_altered = new double[8];

            stats_beyond_cap = new int[8];

            stats_equalized = new double[8];

            for(int i = 0; i < 8; i++)
            {
                stats_original[i] = stats_initial_original[i] + Convert.ToInt32(stats_improved_original[i]);

                if (stats_original[i] > GlobalVariables.stat_cap_min[i])
                {
                    stats_beyond_cap[i] = stats_original[i] - GlobalVariables.stat_cap_min[i];

                    stats_altered[i] = GlobalVariables.stat_cap_min[i];
                }
                else
                {
                    stats_altered[i] = stats_original[i];
                }
            }

            //split_improved_stats();

            ///////////////////////

            equalize_stats();
                        
            ///////////////////////

            for(int i = 0; i < 8; i++)
            {
                stats_equalized[i] += stats_beyond_cap[i] / Convert.ToDouble(GlobalVariables.stat_cap_min[i]) * GlobalVariables.average_of_spreads;
            }

            /////////////////////////////////// take into account that for some reason stamina (energy) regen is determined by initial TU

            stats_equalized[1] *= GlobalVariables.stamina_regen_importance_factors[energy_regen - 16];
        }

        /*private void split_improved_stats()
        {
            stats_improved_up_to_initial_stat_cap = new double[8];
            stats_improved_beyond_initial_stat_cap = new double[8];

            for (int i = 0; i < 8; i++)
            {
                if (stats_altered[i] <= GlobalVariables.stat_initial_max[i]) //if the stat improvements are below the initial stat cap
                {
                    stats_improved_up_to_initial_stat_cap[i] = stats_improved_original[i];
                    //stats_improved_beyond_initial_stat_cap[i] = 0; //redundant
                }
                else //if improvements are above initial stat caps
                {
                    stats_improved_up_to_initial_stat_cap[i] = GlobalVariables.stat_initial_max[i] - stats_initial_original[i];
                    stats_improved_beyond_initial_stat_cap[i] = stats_altered[i] - GlobalVariables.stat_initial_max[i];
                }
            }
        }*/

        private void equalize_stats()
        {
            double[] stat_curves = new double[]
            {
               2, // Time Units
               2, // Stamina
               2, // Health
               1, // Bravery
               1, // Reactions
               1, // Firing Accuracy
               1, // Throwing Accuracy
               2  // Strength
            };

            for (int i = 0; i < 8; i++)
            {
                stats_improved_altered[i] = normalize(curve_stat(stats_altered[i], GlobalVariables.stat_cap_min[i], GlobalVariables.stat_cap_min[i], stat_curves[i]),
                                                      curve_stat(GlobalVariables.stat_initial_min[i], GlobalVariables.stat_cap_min[i], GlobalVariables.stat_cap_min[i], stat_curves[i]),
                                                      GlobalVariables.stat_cap_min[i],
                                                      GlobalVariables.stat_cap_min[i] - GlobalVariables.stat_initial_min[i]);

                stats_altered[i] = GlobalVariables.stat_initial_min[i] + stats_improved_altered[i];

                stats_equalized[i] = normalize(stats_altered[i], GlobalVariables.stat_initial_min[i], GlobalVariables.stat_cap_min[i], GlobalVariables.average_of_spreads);
            }
        }

        private double curve_stat(double stat, double current_max, double wanted_max, double curve)
        {
            return wanted_max - (Math.Pow((current_max-stat)*(Math.Pow(wanted_max,curve)/current_max),1/curve));

            /* //Maple code for calculating the function to make stat increase linear
            restart;
            ligning:= (xp,curve)->Wanted_max-(((Max-xp) * (Wanted_max ^ curve / Max)) ^ (1/curve));
            Wanted_max := 1:
            Max := 100:
            Min := 0:
            y := 4:
            y2 := 2:
            plot((-y/Max)*xp+y,xp=0..Max+10,avg_stat_gain=0..y+2,gridlines);
            plot([(int((-y/Max)*xp+y,xp)),(int((-y2/Max)*xp+y2,xp))],xp=0..Max,gridlines);
            plot([
            (int((-y/Max)*xp+y,xp))/(Max*y/2)* Wanted_max,
            Wanted_max/Max*xp,
            ligning(xp,1/2),
            ligning(xp,2)
            ],xp=Min..Max,y_axis=0..Wanted_max,gridlines);
            restart;
            ligning:= (xp,curve,Max,Wanted_max)->Wanted_max-(((Max-xp) * (Wanted_max ^ curve / Max)) ^ (1/curve));
            evalf(ligning(20,1/2,30,1));
            evalf(ligning(%,2,1,2)/2*30);
            */

            //return Math.Pow(stat * Math.Pow(cap, curve) / cap, 1 / curve);   //(stat * (wanted_max ^ curve / max)) ^ (1 / curve);
        }

        private double normalize(double number,double current_min, double current_max, double wanted_max)
        {
            return (number - current_min) * (wanted_max / (current_max - current_min));
        }

        private void parse_energy_regen()
        {
            switch (stats_initial_original[0]) //stat number zero = TU
            {
                case 50:
                    energy_regen = 16;
                    break;
                case 51:
                case 52:
                case 53:
                    energy_regen = 17;
                    break;
                case 54:
                case 55:
                case 56:
                    energy_regen = 18;
                    break;
                case 57:
                case 58:
                case 59:
                    energy_regen = 19;
                    break;
                case 60:
                    energy_regen = 20;
                    break;
            }
        }

        private void parse_psi_stats()
        {
            psi_strength = entry[50];

            psi_skill    = entry[51];

            if (RunConditions.fix_psi_skill_overflow_bug && (psi_skill > RunConditions.psi_skill_cap || (psi_skill < 16 && psi_skill != 0))) psi_skill = RunConditions.psi_skill_cap;

            if (psi_skill == 0)
            {
                psi_locked = true;
            }
            else
            {
                psi_locked = false;

                psi_attack_strength = calculate_psi_atk_strength(psi_strength, psi_skill);
                psi_defense_strength = calculate_psi_def_strength(psi_strength, psi_skill);

                psi_def_same_format_as_equalized_stats = (psi_defense_strength - calculate_psi_def_strength(0, 16)) / Convert.ToDouble((calculate_psi_def_strength(100, RunConditions.psi_skill_cap) - calculate_psi_def_strength(0, 16))) * GlobalVariables.average_of_spreads;
                psi_atk_same_format_as_equalized_stats = (psi_attack_strength  - calculate_psi_atk_strength(0, 16)) / Convert.ToDouble((calculate_psi_atk_strength(100, RunConditions.psi_skill_cap) - calculate_psi_atk_strength(0, 16))) * GlobalVariables.average_of_spreads;
            }
        }

        private int calculate_psi_atk_strength(int psi_strength, int psi_skill)
        {
            /*
             * http://www.ufopaedia.org/index.php?title=Psionic_Equations
             * 
            Attack Strength (AS) = INT( Psi Strength * Psi Skill / 50 )
            */

            return Convert.ToInt32(Math.Floor(psi_strength * psi_skill / 50.0));
        }

        private int calculate_psi_def_strength(int psi_strength, int psi_skill)
        {
            /*
             * http://www.ufopaedia.org/index.php?title=Psionic_Equations
             * 
            Defense Strength (DS) = INT( Psi Strength + ( Psi Skill/5 ) )
            */

            return Convert.ToInt32(Math.Floor(psi_strength + (psi_skill / 5.0)));
        }

        private void parse_name()
        {
            char[] temp_name = new char[21];
            string temp_name2 = "";
            int name_pos = 0 + space_used_for_level + space_used_for_seperator_level_name;

            bool name_end_found = false; //for some strange reason names of new soldiers consists of a name then a end marker and then traces of old names?!?

            for (int i = 0; i < 21 ; i++)
            {
                if (entry[i + 16] == 0)
                {
                    name_end_found = true;
                }

                if (name_end_found)
                {
                    temp_name2 += temp_name[i] = ' ';
                }
                else
                {
                    temp_name2 += temp_name[i] = Convert.ToChar(entry[i + 16]);
                }
            }

            /////////////////

            if (!temp_name2.Contains(user_input_indicator[0]) && !temp_name2.Contains(user_input_indicator[1]) && !temp_name2.Contains(normal_seperator_marker))
            {
                if (temp_name2.Contains("Dieter") && temp_name2.Contains("Berger"))
                {
                    temp_name2 = "Thor Nielsen         ";
                    temp_name = temp_name2.ToCharArray();
                }

                if (!temp_name2.Contains(special_name_marker))
                {
                    if (temp_name2.Remove(name.Length - 1).Contains(' ') && temp_name2[name.Length - 1].Equals(' ') == false) //adjust computer generated names
                    {
                        for (int i = name.Length - 1; ; i--)
                        {
                            if ( temp_name[i] == ' ' || (temp_name[i] >= 'a' && temp_name[i] <= 'z') || (temp_name[i] >= 'A' && temp_name[i] <= 'Z') ) //if name
                            {
                                if (temp_name[i - 1] == ' ' && ((temp_name[i] >= 'a' && temp_name[i] <= 'z') || (temp_name[i] >= 'A' && temp_name[i] <= 'Z'))
                                   )
                                {
                                    break;
                                }
                                else
                                {
                                    temp_name[i] = ' ';
                                }
                            }
                            else //not letter and not space..?? wtf.. break!
                            {
                                break;
                            }
                        }
                    }
                }

                name_pos = 0;
            }

            //////////////

            name_end_found = false;

            for (int i = 0; i < name.Length; i++)
            {
                if (temp_name[i + name_pos] == user_input_indicator[0] || temp_name[i + name_pos] == user_input_indicator[1] || temp_name[i + name_pos] == normal_seperator_marker)
                {
                    name_end_found = true;
                    
                }

                if (name_end_found)
                {
                    name[i] = Convert.ToByte(' ');
                }
                else if (Convert.ToByte(temp_name[i + name_pos]) == 0)
                {
                    name[i] = Convert.ToByte(' ');
                }
                else
                {
                    name[i] = Convert.ToByte(temp_name[i + name_pos]);
                }
            }

            name[name.Length - 1] = Convert.ToByte(' ');
        }
        
        /////////////////////////////
        
        public void update_file_entry(string file_path)
        {
            if (RunConditions.test_active == false)
            {
                Program.form1.fileSystemWatcher.EnableRaisingEvents = false;

                FileStream fileStream;

                byte[] new_file_entry = new byte[68];

                generate_new_file_entry().CopyTo(new_file_entry, 0);

                do
                {
                    try
                    {
                        fileStream = new FileStream(file_path, FileMode.Open, FileAccess.Write, FileShare.None);

                        fileStream.Position = entry_index;

                        fileStream.Write(new_file_entry, 0, 68);

                        fileStream.Close();

                        Program.form1.fileSystemWatcher.EnableRaisingEvents = true;

                        return;

                    }
                    catch
                    {
                        System.Threading.Thread.Sleep(GlobalVariables.sleep_time_between_file_open_tries); //wait and try again
                    }

                } while (true); //continue forever
            }
        }

        private byte[] generate_new_file_entry()
        {
            byte[] new_entry = new byte[68];

            entry.CopyTo(new_entry, 0);

            int i = 16;

            //level
            if (alive)
            {
                if (Level >= 0) //an important fact to understand this code is that the highest value level can attain is Math.Pow(10, space_used_for_level)) - 1
                {
                    for (int j = space_used_for_level - 1; j >= 0; j--)  //hvis 3 så 2 1 0
                    {
                        int cifre = Convert.ToInt32(Level / Math.Pow(10, j));

                        if (cifre != 0)
                        {
                            new_entry[i++] = Convert.ToByte(cifre + '0');
                        }
                        else
                        {
                            new_entry[i++] = Convert.ToByte(' ');
                        }
                    }
                }
                else
                {
                    for (int j = 0; j < space_used_for_level - 1; j++)
                    {
                        new_entry[j++] = Convert.ToByte(' ');
                    }

                    new_entry[i++] = Convert.ToByte(did_not_qualify_for_a_class_marker);
                }
            }
            //

            //seperator
            if (alive)
            {
                if (level_up) new_entry[i++] = Convert.ToByte(level_up_marker);
                else          new_entry[i++] = Convert.ToByte(' ');
            }
            //

            //name

            for (int j = 0; j < name.Length; j++)
            {
                new_entry[i++] = name[j];
            }

            //

            //seperator
            
            //new_entry[i-1] = Convert.ToByte(' '); //just to make sure
            if (alive)
            {
                if (UserDeterminedClass != 0) new_entry[i++] = Convert.ToByte(user_input_indicator[UserDeterminedClass - 1]);

                else new_entry[i++] = Convert.ToByte(normal_seperator_marker);

                if (class_switch) new_entry[i++] = Convert.ToByte(class_switch_marker);
                else              new_entry[i++] = Convert.ToByte(' ');
            }
            //

            //class
            if (alive)
            {
                foreach (byte b in Class_to_byte_array()) new_entry[i++] = b;
            }
            // To fix wierd bug, in game only ~21 chars can be edited, however the name is actually 25 char's long. To make sure i chose to use only the 21 char's (in order for user to able to write a user determined class). But the end of the name MUST be a null terminator, otherwise the game will produce graphical errors in the windows version of the game

            while(i < 16+25) new_entry[i++] = 0x00;

            //

            if (i > 16 + 25) throw (new Exception("Unknown error, non name char's edited")); //could change the program to simply not be able to..

            //

            /*for (int counter = 16; counter < (16 + 25) && new_entry[counter] != 0; counter++)
            {
                new_name_string += Convert.ToChar(new_entry[counter]);
            }

            new_name_string.TrimEnd(' ');*/

            //

            if (RunConditions.fix_psi_skill_overflow_bug && (psi_skill > RunConditions.psi_skill_cap || (psi_skill < 16 && psi_skill != 0))) new_entry[51] = RunConditions.psi_skill_cap; 

            return new_entry;
        }

        /////////////////////////////

        public byte[] Class_to_byte_array()
        {
            char[] chosen_class_name = GlobalVariables.classes[chosen_class_number].ToCharArray();

            byte[] output = new byte[space_used_for_class];

            for (int i = 0; i < space_used_for_class; i++)
            {
                if (i < chosen_class_name.Length) output[i] = Convert.ToByte(chosen_class_name[i]);
                else output[i] = Convert.ToByte(' ');
            }

        return output;
        }
    }
}




