context: using kOS.AddOns.RemoteTech;
  using kOS.Binding;
  using kOS.Execution;
  using kOS.Factories;
  using kOS.Function;
  using kOS.Communication;
  using kOS.Persistence;
  using kOS.Safe;
  using kOS.Safe.Compilation;
  using kOS.Safe.Compilation.KS;
  using kOS.Safe.Module;
  using kOS.Safe.Persistence;
  using kOS.Safe.Screen;
  using kOS.Safe.Utilities;
  using kOS.Utilities;
  using KSP.IO;
  using System;
  using System.Collections.Generic;
  using System.Linq;
  using kOS.Safe.Execution;
  using UnityEngine;
  using kOS.Safe.Encapsulation;
  using KSP.UI;
  using kOS.Suffixed;
  using kOS.Safe.Communication;
  
  namespace kOS.Module
  {
      public class kOSProcessor : PartModule, IProcessor, IPartCostModifier, IPartMassModifier
      {
          public ProcessorModes ProcessorMode { get; private set; }
  
          public Harddisk HardDisk { get; private set; }
  
          public MessageQueue Messages { get; private set; }
  
          public string Tag
          {
              get
              {
                  KOSNameTag tag = part.Modules.OfType<KOSNameTag>().FirstOrDefault();
                  return tag == null ? string.Empty : tag.nameTag;
              }
          }
  
          private int vesselPartCount;
          private SharedObjects shared;
          private static readonly List<kOSProcessor> allMyInstances = new List<kOSProcessor>();
          private bool firstUpdate = true;
  
          private MovingAverage averagePower = new MovingAverage();
  
          // This is the "constant" byte count used when calculating the EC 
          // required by the archive volume (which has infinite space).
          // TODO: This corresponds to the existing value and should be adjusted for balance.
          private const int ARCHIVE_EFFECTIVE_BYTES = 50000;
  
          //640K ought to be enough for anybody -sic
          private const int PROCESSOR_HARD_CAP = 655360;
  
          [KSPField(isPersistant = true, guiActive = false, guiActiveEditor = true, guiName = "Boot File"), UI_ChooseOption(scene = UI_Scene.Editor)]
          public string bootFile = "boot.ks";
  
          [KSPField(isPersistant = true, guiName = "kOS Disk Space", guiActive = true)]
          public int diskSpace = 1024;
  
          [KSPField(isPersistant = true, guiName = "kOS Base Disk Space", guiActive = false)]
          public int baseDiskSpace = 0;
  
          [KSPField(isPersistant = true, guiName = "kOS Base Module Cost", guiActive = false)]
          public float baseModuleCost = 0F;
  
          [KSPField(isPersistant = true, guiName = "kOS Base Part Mass", guiActive = false)]
          public float basePartMass = 0F;
  
          [KSPField(isPersistant = false, guiName = "kOS Disk Space", guiActive = false, guiActiveEditor = true), UI_ChooseOption(scene = UI_Scene.Editor)]
          public string diskSpaceUI = "1024";
  
          [KSPField(isPersistant = true, guiName = "CPU/Disk Upgrade Cost", guiActive = false, guiActiveEditor = true)]
          public float additionalCost = 0F;
  
          [KSPField(isPersistant = false, guiName = "CPU/Disk Upgrade Mass", guiActive = false, guiActiveEditor = true)]
          public float additionalMass = 0F;
  
          [KSPField(isPersistant = true, guiActive = false)]
          public int MaxPartId = 100;
  
          // This represents how much EC to consume per executed instruction.
          // This would be the "variable" component of the processor's power.
          // Important: This value should be overriden in the part.cfg file
          // for the kOS processor.  The only reason it's being given a value
          // here is as a fallback for those cases where an old legacy part
          // might be loaded from before the part files had this value.
          [KSPField(isPersistant = false, guiActive = false)]
          public float ECPerInstruction = 0.000004F;
  
          // This represents how much EC to consume per Byte of the current volume, per second.
          // This would be the "continuous" compoenent of the processor's power (though it varies
          // when you change to another volume).
          // IMPORTANT: The value defaults to zero and must be overriden in the module
          // definition for any given part (within the part.cfg file).
          [KSPField(isPersistant = false, guiActive = false)]
          public float ECPerBytePerSecond = 0F;
  
          public kOSProcessor()
          {
              ProcessorMode = ProcessorModes.READY;
          }
  
          [KSPEvent(guiActive = true, guiName = "Open Terminal", category = "skip_delay;")]
          public void Activate()
          {
              SafeHouse.Logger.Log("Activate");
              OpenWindow();
          }
  
          [KSPField(isPersistant = true, guiName = "kOS Average Power", guiActive = true, guiActiveEditor = true, guiUnits = "EC/s", guiFormat = "0.000")]
          public float RequiredPower = 0;
  
          [KSPEvent(guiActive = true, guiName = "Toggle Power")]
          public void TogglePower()
          {
              SafeHouse.Logger.Log("Toggle Power");
              ProcessorModes newProcessorMode = (ProcessorMode != ProcessorModes.OFF) ? ProcessorModes.OFF : ProcessorModes.STARVED;
              SetMode(newProcessorMode);
          }
  
          [KSPAction("Open Terminal", actionGroup = KSPActionGroup.None)]
          public void Activate(KSPActionParam param)
          {
              SafeHouse.Logger.Log("Open Terminal from Dialog");
              Activate();
          }
  
          [KSPAction("Close Terminal", actionGroup = KSPActionGroup.None)]
          public void Deactivate(KSPActionParam param)
          {
              SafeHouse.Logger.Log("Close Terminal from ActionGroup");
              CloseWindow();
          }
  
          [KSPAction("Toggle Terminal", actionGroup = KSPActionGroup.None)]
          public void Toggle(KSPActionParam param)
          {
              SafeHouse.Logger.Log("Toggle Terminal from ActionGroup");
              ToggleWindow();
          }
  
          [KSPAction("Toggle Power", actionGroup = KSPActionGroup.None)]
          public void TogglePower(KSPActionParam param)
          {
              SafeHouse.Logger.Log("Toggle Power from ActionGroup");
              TogglePower();
          }
          public void OpenWindow()
          {
              shared.Window.Open();
          }
  
          public void CloseWindow()
          {
              shared.Window.Close();
          }
  
          public void ToggleWindow()
          {
              shared.Window.Toggle();
          }
  
          public bool WindowIsOpen()
          {
              return shared.Window.IsOpen;
          }
  
          public bool TelnetIsAttached()
          {
              return shared.Window.NumTelnets() > 0;
          }
  
          public IScreenBuffer GetScreen()
          {
              return shared.Screen;
          }
  
          // TODO - later refactor making this kOS.Safer so it can work on ITermWindow, which also means moving all of UserIO's classes too.
          public Screen.TermWindow GetWindow()
          {
              return shared.Window;
          }
  
          //returns basic information on kOSProcessor module in Editor
          public override string GetInfo()
          {
              int defaultAvgInstructions = 200;
              string format =
                  "Default disk capacity: {0}\n\n" +
                  "<color=#99ff00ff>Requires:</color>\n" +
                  " - ElectricCharge: {1}\n" +
                  "<color=#99ff00ff>Example:</color>\n" +
                  " - {2:N3}EC/s if IPU={3} and no wait instructions.";
              // For the sake of GetInfo, prorate the EC usage based on the smallest physics frame currently selected
              // Because this is called before the part is set, we need to manually calculate it instead of letting Update handle it.
              double power = diskSpace * ECPerBytePerSecond + defaultAvgInstructions * ECPerInstruction / Time.fixedDeltaTime;
              string chargeText = (ECPerInstruction == 0) ? 
                  "None.  It's powered by pure magic ... apparently." : // for cheaters who use MM or editing part.cfg, to get rid of it.
                  string.Format("1 per {0} instructions executed", (int)(1 / ECPerInstruction));
              return string.Format(format, diskSpace, chargeText, power, defaultAvgInstructions);
          }
  
          //implement IPartCostModifier component
          public float GetModuleCost(float defaultCost, ModifierStagingSituation sit)
          {
              // the 'sit' arg is irrelevant to us, but the interface requires it.
  
              return additionalCost;
          }
          //implement IPartMassModifier component
          public ModifierChangeWhen GetModuleCostChangeWhen()
          {
              return ModifierChangeWhen.FIXED;
          }
  
          private void UpdateCostAndMass()
          {
              const float DISK_SPACE_MASS_MULTIPLIER = 0.0000048829F; //implies approx 20kg for 4096bytes of diskSpace
              const float DISK_SPACE_COST_MULTIPLIER = 0.0244140625F; //implies approx 100funds for 4096bytes of diskSpace
  
              additionalCost = baseModuleCost + (float)System.Math.Round((diskSpace - baseDiskSpace) * DISK_SPACE_COST_MULTIPLIER, 0);
              additionalMass = (diskSpace - baseDiskSpace) * DISK_SPACE_MASS_MULTIPLIER;
  
              part.mass = basePartMass + additionalMass;
          }
  
          //implement IPartMassModifier component
          public float GetModuleMass(float defaultMass, ModifierStagingSituation sit)
          {
              // the 'sit' arg is irrelevant to us, but the interface requires it.
              
              return part.mass - defaultMass; //copied this fix from ProceduralParts mod as we already changed part.mass
              //return additionalMass;
          }
          //implement IPartMassModifier component
          public ModifierChangeWhen GetModuleMassChangeWhen()
          {
              return ModifierChangeWhen.FIXED;
          }
  
          public override void OnStart(StartState state)
          {
              //if in Editor, populate boot script selector, diskSpace selector and etc.
              if (state == StartState.Editor)
              {
                  if (baseDiskSpace == 0)
                      baseDiskSpace = diskSpace;
  
                  if (System.Math.Abs(baseModuleCost) < 0.000001F)
                      baseModuleCost = additionalCost;  //remember module cost before tweaks
                  else
                      additionalCost = baseModuleCost; //reset module cost and update later in UpdateCostAndMass()
  
                  if (System.Math.Abs(basePartMass) < 0.000001F)
                      basePartMass = part.mass;  //remember part mass before tweaks
                  else
                      part.mass = basePartMass; //reset part mass to original value and update later in UpdateCostAndMass()
  
                  InitUI();
              }
  
              UpdateCostAndMass();
  
              //Do not start from editor and at KSP first loading
              if (state == StartState.Editor || state == StartState.None)
              {
                  return;
              }
  
              SafeHouse.Logger.Log(string.Format("OnStart: {0} {1}", state, ProcessorMode));
              InitObjects();
          }
  
          private void InitUI()
          {
              //Populate selector for boot scripts
              BaseField field = Fields["bootFile"];
              var options = (UI_ChooseOption)field.uiControlEditor;
  
              var bootFiles = new List<string>();
  
              var temp = new Archive();
              var files = temp.FileList;
              var maxchoice = 0;
              bootFiles.Add("None");
              foreach (KeyValuePair<string, VolumeFile> pair in files)
              {
                  if (!pair.Key.StartsWith("boot", StringComparison.InvariantCultureIgnoreCase)) continue;
                  bootFiles.Add(pair.Key);
                  maxchoice++;
              }
              //no need to show the control if there are no files starting with boot
              options.controlEnabled = maxchoice > 0;
              field.guiActiveEditor = maxchoice > 0;
              options.options = bootFiles.ToArray();
  
              //populate diskSpaceUI selector
              diskSpaceUI = diskSpace.ToString();
              field = Fields["diskSpaceUI"];
              options = (UI_ChooseOption)field.uiControlEditor;
              var sizeOptions = new string[3];
              sizeOptions[0] = baseDiskSpace.ToString();
              sizeOptions[1] = (baseDiskSpace * 2).ToString();
              sizeOptions[2] = (baseDiskSpace * 4).ToString();
              options.options = sizeOptions;
          }
  
          public void InitObjects()
          {
              SafeHouse.Logger.LogWarning("InitObjects: " + (shared == null));
  
              shared = new SharedObjects();
              CreateFactory();
  
              shared.Vessel = vessel;
              shared.Processor = this;
              shared.KSPPart = part;
              shared.UpdateHandler = new UpdateHandler();
              shared.BindingMgr = new BindingManager(shared);
  
gt:             shared.Interpreter = shared.Factory.CreateInterpreter(shared);