﻿using BNEBotCore;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BNEBotLauncher
{
  public class BotProcess
  {
    public Process Proc
    {
      get;
      private set;
    }

    public BotInfo Bot
    {
      get;
      private set;
    }

    static BotProcess()
    {
      m_BotCounter = LaunchSettings.FirstBotIndex;
    }

    // Start a new bot using the specified .exe and command line arguments
    public static BotProcess StartNew(string exePath, string strategy = "", int targetLevel = 0, params string[] args)
    {
      // Generate a unique name for the bot
      string botName = "";
      if (LaunchSettings.UniqueBots)
      {
        botName = DateTime.Now.ToFileTimeUtc().ToString() + (m_BotCounter++).ToString("D5") + "-" + exePath;
      }
      else
      {
        botName = (m_BotCounter++).ToString("D5") + "-" + exePath;
      }

      // Crate data directory for the bot
      string sourceDataPath = Path.Combine(Environment.CurrentDirectory, "bot");
      string destinationDataPath = Path.Combine(Path.Combine(Environment.CurrentDirectory, "bot-run-" + m_RunId.ToString()), botName);

      if (Directory.Exists(destinationDataPath))
      {
        Directory.Delete(destinationDataPath, true);
      }

      // Copy bot data to its directory
      IOUtils.DirCopy(sourceDataPath, destinationDataPath, false);

      // Prepare the bot info
      BotInfo botInfo = new BotInfo()
      {
        Name = botName,
        State = BotState.Idle
      };

      // Prepare the command line
      string cmdLine = string.Join(" ", args);
      cmdLine += "--datapath " + "\"" + destinationDataPath + "\"";
      cmdLine += " !bot.enabled";
      cmdLine += " !bot.online=" + (LaunchSettings.LocalBot ? "false" : "true");
      cmdLine += " !bot.strategy=" + (string.IsNullOrEmpty(strategy) ? LaunchSettings.DefaultBotStrategy : strategy);
      cmdLine += " !bot.target=" + (targetLevel == 0 ? LaunchSettings.DefaultBotTargetLevel : targetLevel);
      cmdLine += " !bot.user=" + botName;

      // Spawn the process
      Process proc = new Process();

      proc.EnableRaisingEvents = true;
      proc.StartInfo.UseShellExecute = false;
      proc.StartInfo.CreateNoWindow = true;

      // If we are running under *nix, we should use the wine executable as file name and the actual bot exe as the first argument
      if (LaunchSettings.UseWine)
      {
        proc.StartInfo.FileName = "wine";
        cmdLine = Path.Combine("bin", exePath) + " " + cmdLine;
      }
      else
      {
        proc.StartInfo.FileName = Path.Combine("bin", exePath);
      }

      proc.StartInfo.Arguments = cmdLine;
      proc.StartInfo.WorkingDirectory = Environment.CurrentDirectory; // Let the bot execute where we've invoked the launcher from

      //proc.StartInfo.RedirectStandardOutput = true;
      //proc.StartInfo.RedirectStandardError = true;

      BotProcess bot = new BotProcess()
      {
        Proc = proc,
        Bot = botInfo
      };

      // Listen for exit and update state
      proc.Exited += (o, a) =>
      {
        if (proc.ExitCode != 0)
        {
          // Check for the bot success flag file to determine if the bot has crashed during runtime or not
          if (File.Exists(Path.Combine(destinationDataPath, "SUCCESS")))
          {
            // Normal exit
            botInfo.State = BotState.Stopped;
          }
          else
          {
            // Crash
            botInfo.State = BotState.Crashed;
          }
        }
        else
        {
          // Normal exit
          botInfo.State = BotState.Stopped;
        }
      };

      if (proc.Start())
      {
        botInfo.State = BotState.Running;

        // TEST: perf stats
        //PerformanceCounterCategory cat = new PerformanceCounterCategory("Process");

        //string[] instances = cat.GetInstanceNames();
        //string instanceName = string.Empty;
        //foreach (string instance in instances)
        //{

        //  using (PerformanceCounter cnt = new PerformanceCounter("Process",
        //       "ID Process", instance, true))
        //  {
        //    int val = (int)cnt.RawValue;
        //    if (val == proc.Id)
        //    {
        //      instanceName = instance;
        //      break;
        //    }
        //  }
        //}

        //bot.m_CpuCounter = new PerformanceCounter("Process", "% Processor Time", instanceName);
        //bot.m_MemCounter = new PerformanceCounter("Process", "Working Set", instanceName);

        return bot;
      }
      else
      {
        // Failed to start!
        throw new Exception("Failed to start bot from executable " + exePath);
      }
    }

    // Restart the bot
    public bool Restart()
    {
      if (Proc.Start())
      {
        Bot.ForceState(BotState.Running);
        Bot.LaunchTimestamp = DateTime.Now;
        return true;
      }
      else
      {
        return false;
      }
    }

    // Kill the bot
    public void Kill()
    {
      if (!Proc.HasExited)
      {
        Proc.Kill();
        if (Bot.State == BotState.Running)
        {
          Bot.State = BotState.Killed;
        }        
      }
    }

    // Update performance stats
    public void UpdatePerfStats()
    {
      if (m_CpuCounter != null && m_MemCounter != null)
      {
        try
        {
          Bot.CPUUsage = m_CpuCounter.NextValue();
          Bot.MemoryUsage = m_MemCounter.NextValue() / 1024 / 1024; // to MB
        }
        catch (Exception)
        {
          // Process no longer available, clear the counters
          m_CpuCounter = null;
          m_MemCounter = null;
        }
      }
    }

    // Unique ID of this run (will be shared by all bots spawned by this process instance)
    private static Guid m_RunId = Guid.NewGuid();

    // Bot counter for a single process run
    private static int m_BotCounter;

    // Load counters
    private PerformanceCounter m_CpuCounter;
    private PerformanceCounter m_MemCounter;
  }
}
