using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Connexion.Core.HL7;
using Connexion.Share;
using InnerHarbour.DataGeneration;

namespace MongoPerfTest
{
  public class Generator
  {
    // generation vars
    const int _numberOfEpisodesToGenerate = 1000000;  // number of Episodes to create (6 messages in an Episode)
    const int _numberOfEpisodesPerSecond = 100;   // configure the number of Episodes per second (6 messages in an Episode)
    private readonly DataGenerator _data = new DataGenerator();
    CancellationTokenSource _cancellationTokenSource;
     DateTime _firstDateTime = new DateTime(2011,06,01); // Date time of the first message
    DateTime _eventDateTime;
    Person _patient, _nextOfKin, _attendingPhysician, _referringPhysician, _orderingProvider, _assistantResultInterpreter, _principalResultInterpreter, _technician, _transcriptionist;
    string[] _pv1_2 = {"E","I","O","R","U"};  // patient class
    string[] _pv1_4 = {"A","C","E","L","N","R","U"}; // Admission Type
    string[] _pv1_18 = {"IN","ER","CLI","REF","INO","SDC","RCR"}; // patient Type
    string[] _in1_2 = {"MDCR", "SPAY", "UNIT HLTH", "BLUE", "HLTPLUS", "VETS", "BLSH OPPO", "PREM LIFE", "TRICARE", "PRUDENT", "FARMERS","MILITARY"}; // Insurance plan
    int _counter;
    string _fillerOrderNum;
    private MessageChannelDevice m_MessageChannel;

    public Generator()
    {
      _eventDateTime = _firstDateTime;
      var mc = new MessageChannel(Guid.NewGuid().ToString("N"));
    }
    
    private void SetEpisodeData(){
     _eventDateTime = _firstDateTime.AddSeconds(_counter*30);
     _patient = _data.GeneratePerson(Sex.Any);
     _nextOfKin = _data.GeneratePerson(Sex.Any);
     _nextOfKin.FamilyName = _patient.FamilyName;
     _attendingPhysician = _data.GetRandomProvider();
     _referringPhysician = _data.GetRandomProvider();
     _orderingProvider = _data.GetRandomProvider();
     _assistantResultInterpreter = _data.GetRandomProvider();
     _principalResultInterpreter = _data.GetRandomProvider();
     _technician = _data.GetRandomProvider();
     _transcriptionist = _data.GetRandomProvider();
      var now = DateTime.Now;
     _patient.Identifier = string.Format("{0}{1}.{2}", now.Year-2010, now.ToString("MMddHHmmss"), _counter);
     _fillerOrderNum = _patient.Identifier.Replace(".","");
    }

    private void UpdateMsh(HL7Message message, string messageType){
      MSH msh = message.MSH;
      msh.MessageType_09.Set(messageType);
      msh.DateTimeOfMessage_07.Value = _eventDateTime.ToString("yyyyMMddhhmmss.ff");
      msh.MessageControlID_10.Value = string.Format("IHS-{0}.{1:D5}",_eventDateTime.ToString("yyyyMMddhhmmss"),_counter);
    }

    private void AddPid(HL7Message message){
     PID pid = new PID();
     message.Segments.Append(pid);
     pid.SetID_PID_01.Value="1";
     pid.PatientIdentifierList_03.First.IDNumber_01.Value = _patient.Identifier;
     pid.DateTimeOfBirth_07.Value = _patient.BirthDate;
     pid.AdministrativeSex_08.Value = _patient.Sex;
     pid.SSNNumber_Patient_19.Value = _patient.SSN;
     pid.PatientAccountNumber_18.Set(_patient.SSN);
     SetXpnFromPerson(pid.PatientName_05.First,_patient);
     SetXadFromAddress(pid.PatientAddress_11.First,_patient.Address);
    }

    private void AddPv1(HL7Message message){
      PV1 pv1 = new PV1();
      message.Segments.Append(pv1);
      pv1.SetID_PV1_01.Value = "1";
      pv1.PatientClass_02.Value = _data.GetRandomValue(_pv1_2);
      pv1.AdmissionType_04.Value = _data.GetRandomValue(_pv1_4);
      pv1.AssignedPatientLocation_03.Set(string.Format("P{0}^R{1}^B{2}",_data.GetRandomInt(100,200),_data.GetRandomInt(1,20),_data.GetRandomInt(1,10)));
      SetXcnFromPerson(pv1.AttendingDoctor_07.First,_attendingPhysician);
      SetXcnFromPerson(pv1.ReferringDoctor_08.First,_referringPhysician);
      pv1.AdmitSource_14.Value = _data.GetRandomInt(1,9).ToString();
      pv1.PatientType_18.Value = _data.GetRandomValue(_pv1_18);
      pv1.AdmitDateTime_44.Value=_eventDateTime.ToString("yyyyMMddhhmmss");
    }

    private void AddNk1(HL7Message message){
     NK1 nk1 = new NK1();
     message.Segments.Append(nk1);
     nk1.SetID_NK1_01.Value = "1";
     SetXpnFromPerson(nk1.Name_02.First,_nextOfKin);
     nk1.Relationship_03.Identifier_01.Value = _nextOfKin.Sex=="M" ? "FA" : "MO";
    }

    private void AddIn1(HL7Message message){
      IN1 in1 = new IN1();
      message.Segments.Append(in1);
      in1.SetID_IN1_01.Value = "1";
      in1.InsurancePlanID_02.Set(_data.GetRandomValue(_in1_2));
      SetXpnFromPerson(in1.NameOfInsured_16.First,_patient);
      in1.InsuredSRelationshipToPatient_17.Set("SELF");
    }

    private void AddResult(HL7Message message){
      OBR obr = message.Segments.OfType<OBR>().First();
      obr.FillerOrderNumber_03.Set(_fillerOrderNum);
      SetNdlFromPerson(obr.PrincipalResultInterpreter_32,_principalResultInterpreter);
      SetNdlFromPerson(obr.AssistantResultInterpreter_33.First,_assistantResultInterpreter);
      SetNdlFromPerson(obr.Transcriptionist_35.First,_transcriptionist);
      string[] results = new string[]{ 
        string.Format("Results for patient: {0} {1}", _patient.GivenName, _patient.FamilyName),  
        string.Format("Exam {0}: {1}",obr.ScheduledDateTime_36.Value, obr.UniversalServiceIdentifier_04.Text_02.Value),
        string.Format("Referring Physician: Dr. {0}, {1}",_referringPhysician.GivenName, _referringPhysician.FamilyName),
        string.Format("Attending Physician: Dr. {0}, {1}",_attendingPhysician.GivenName, _attendingPhysician.FamilyName),
        string.Format("Principal Result Interpreter: Dr. {0}, {1}",_principalResultInterpreter.GivenName, _principalResultInterpreter.FamilyName),    
      };
      for(int i=0; i<results.Length;++i){
       OBX obx =  new OBX();
       message.Segments.Append(obx);
       obx.SetID_OBX_01.Value = (i+1).ToString();
       obx.ValueType_02.Value = "TX";
       obx.ObservationValue_05.Set(results[i]);
       obx.ObservationResultStatus_11.Value="P";
      }
      
    }

    private void SetNdlFromPerson(NDL ndl, Person provider) {
       ndl.Name_01.IDNumber_01.Value = provider.SSN;
       ndl.Name_01.FamilyName_02.Value = provider.FamilyName;
       ndl.Name_01.GivenName_03.Value = provider.GivenName;
       ndl.Name_01.SecondAndFurtherGivenNamesOrInitialsThereof_04.Value = provider.MiddleName;
       ndl.Name_01.Prefix_06.Value = "DR";
       ndl.Name_01.Suffix_05.Value = "MD";
     }

    private void AddExam(HL7Message message){
      ORC orc = new ORC();
      message.Segments.Append(orc);
      orc.OrderControl_01.Value="NW";
      orc.PlacerOrderNumber_02.Set(_eventDateTime.ToString("yMMddhhmmss")+"."+_counter.ToString());
      orc.OrderStatus_05.Value = "SC";
      orc.DateTimeOfTransaction_09.Value = _eventDateTime.ToString("yyyyMMddhhmmss");
      SetXcnFromPerson(orc.EnteredBy_10.First,_technician);
      SetXcnFromPerson(orc.OrderingProvider_12.First,_orderingProvider);
      
      OBR obr = new OBR();
      message.Segments.Append(obr);
      obr.PlacerOrderNumber_02.Set(orc.PlacerOrderNumber_02.ToString());
      obr.FillerOrderNumber_03.Set(_fillerOrderNum);
      ProcedureCode procedure = _data.GetRandomProcedureCode();
      obr.UniversalServiceIdentifier_04.Identifier_01.Value=procedure.Code;
      obr.UniversalServiceIdentifier_04.Text_02.Value = procedure.Description;
      obr.OrderingProvider_16.Set(orc.OrderingProvider_12.ToString());
      obr.ResultStatus_25.Value = "S";
      obr.OrderCallbackPhoneNumber_17.Set(_orderingProvider.WorkNumber);
      obr.ScheduledDateTime_36.Value = orc.DateTimeOfTransaction_09.Value;
    }

    private void UpdateExam(HL7Message message, string orc2, string orc5, string obr25){
      ORC orc = message.Segments.OfType<ORC>().First();
      orc.OrderControl_01.Value=orc2;
      orc.OrderStatus_05.Value=orc5;
      OBR obr = message.Segments.OfType<OBR>().First();
      obr.ResultStatus_25.Value = obr25;
    }

    private void UpdateResult(HL7Message message, string obr25){
      OBR obr = message.Segments.OfType<OBR>().First();
      obr.ResultStatus_25.Value = obr25;
      foreach(OBX obx in message.Segments.OfType<OBX>()){
       obx.ObservationResultStatus_11.Value = obr25; 
      }
    }

    private void SetXadFromAddress(XAD xad, Address address){
      xad.StreetAddress_01.StreetOrMailingAddress_01.Value = address.MailingAddress;
      xad.City_03.Value = address.City;
      xad.StateOrProvince_04.Value = address.StateOrProvince;
      xad.Country_06.Value = address.Country;
      xad.OtherDesignation_02.Value = address.OtherGeographicDesignation;
      xad.ZipOrPostalCode_05.Value = address.ZipOrPostalCode;
    }

    private void SetXpnFromPerson(XPN xpn, Person person){
      xpn.FamilyName_01.Surname_01.Value = person.FamilyName;
      xpn.GivenName_02.Value = person.GivenName;
      xpn.SecondAndFurtherGivenNamesOrInitialsThereof_03.Value = person.MiddleName;
      xpn.Degree_06.Value = person.Degree;
      if(person.Degree == "PHD") xpn.Prefix_05.Value = "Dr.";
    }

    private void SetXcnFromPerson(XCN xcn, Person provider){
      xcn.IDNumber_01.Value = provider.SSN;
      xcn.FamilyName_02.Surname_01.Value = provider.FamilyName;
      xcn.GivenName_03.Value = provider.GivenName;
      xcn.SecondAndFurtherGivenNamesOrInitialsThereof_04.Value = provider.MiddleName;
      xcn.Prefix_06.Value = "DR";
      xcn.Suffix_05.Value = "MD";
    }
     
    public void Start()
    {
      _cancellationTokenSource = new CancellationTokenSource();
      QueueMessages(_cancellationTokenSource.Token);
    }
    
    public void Stop()
    {   
      var temp = _cancellationTokenSource;
      if(temp != null)
        temp.Cancel();
    }
    
    private void QueueMessages(CancellationToken ct)
    {     
      var generateTask = new Task(async () =>
      {
        await Task.Delay(5000);
        
        for (_counter = 0; _counter < _numberOfEpisodesToGenerate; ++_counter)
        { 
          //sw.Restart();
          
          if(ct.IsCancellationRequested)
            break;
          
          SetEpisodeData();
          // genrate an ADT^A04 message
          var message = new HL7Message(@"MSH|^~\&|ADM|SHM|ALL|ALL|||||P|2.1");
          UpdateMsh(message, "ADT^A04");
          AddPid(message); // Add a PID Segment
          AddPv1(message); // Add a PV1 Segment
          AddNk1(message); // Add an NK1 Segment
          AddIn1(message); // Add an IN1 Segment
          var context = m_MessageChannel.CreateMessageContext(message);
          await m_MessageChannel.PostOnChannelAsync(context);
          
          // change the patient birthdate
          PID pid = message.Segments.OfType<PID>().First();
          pid.DateTimeOfBirth_07.Value = DateTime.ParseExact(_patient.BirthDate, "yyyyMMdd",null).AddYears(1).ToString("yyyyMMdd");
          _eventDateTime = _eventDateTime.AddMinutes(5);
          UpdateMsh(message, "ADT^A08"); // change the message to and ADT^A08   
          context = m_MessageChannel.CreateMessageContext(message);
          await m_MessageChannel.PostOnChannelAsync(context);
          
          // schedule an exam using an ORM message
          _eventDateTime = _eventDateTime.AddMinutes(60);
          UpdateMsh(message, "ORM^O01");
          AddExam(message);
          context = m_MessageChannel.CreateMessageContext(message);
          await m_MessageChannel.PostOnChannelAsync(context);
                    
          // change the exam to the completed state
          _eventDateTime = _eventDateTime.AddMinutes(120);
          UpdateMsh(message, "ORM^O01");
          UpdateExam(message,"RE","CM","P");
          context = m_MessageChannel.CreateMessageContext(message);
          await m_MessageChannel.PostOnChannelAsync(context);
                    
          // add results to the message
          _eventDateTime = _eventDateTime.AddMinutes(180); // save the message
          UpdateMsh(message, "ORU^R01");
          AddResult(message);
          // UpdateResult(message,"P");
          context = m_MessageChannel.CreateMessageContext(message);
          await m_MessageChannel.PostOnChannelAsync(context);
                    
          // change the state from Preliminary to Final
          UpdateMsh(message, "ORU^R01");
          UpdateResult(message,"F");
          context = m_MessageChannel.CreateMessageContext(message);
          await m_MessageChannel.PostOnChannelAsync(context);
        }
      });

      generateTask.Start();
    }
  }
}
