unit MachSeq;

interface
uses comctrls, MachDev_Base;

Type
  TSeqItemTypes = (siMACH, siVAL, siTDAT, siDEL, siMSTOP, siMBUTT);

  PSeqRec =^TSeqRec;
  TSeqRec = record
    ItemType: TSeqItemTypes;
    Item: pointer;
  end;

  PMachSeqRec =^TMachSeqRec;
  TMachSeqRec = record
    TagName: string[32];
    TagCtrlIdx: integer;
    TagMasterOutIdx: integer;
    TagSostIdx: integer;
  end;

  PValSeqRec =^TValSeqRec;
  TValSeqRec = record
    TagName: string[64];
    TagIdx: integer;
    SetValue: integer;
    ResValue: integer;
  end;

  PTdatSeqRec =^TTdatSeqRec;
  TTdatSeqRec = record
    TagName: string[64];
    TagIdx: integer;
    SetValue: integer;
  end;

  PDelSeqRec =^TDelSeqRec;
  TDelSeqRec = record
    OnStart: boolean;
    OnStop: boolean;
    DelTime: integer;
    Cnt: integer;
  end;

  PMstopSeqRec =^TMstopSeqRec;
  TMstopSeqRec = record
    TagIdx: integer;
    SetValue: integer;
    DelTime: integer;
    Cnt: integer;
    TagName: string[64];
    MesStr: string[128];
  end;

  PMbuttSeqRec =^TMbuttSeqRec;
  TMbuttSeqRec = record
    TagStartIdx: integer;
    TagStopIdx: integer;
    TagStartName: string[64];
    TagStopName: string[64];
    Trigger: boolean;
    StopFlag: boolean;
  end;


  TMachSeq = class
  private
    RepeatFlag: boolean;
    function CanStop(s: string): boolean;
    procedure LoadMsqItem(s: string; var idx: integer);
    procedure LoadMsqGroup(GroupName: string; var idx: integer);
  protected
    TagBeepStartIdx: integer;
  public
    Items: array of TSeqRec;
    ItemCount: integer;
    MaxItemCount: integer;

    Name: string[128];
    TagPath: string[8];
    State: integer;
    StateCnt, StartStateCnt: integer;
    CurMach: string;
    TmpMachIdx, TmpMachCnt: integer;
    SharedMachs: string;
    LoadSuccess: boolean;
    s_err: string;

    List: TListView;

    constructor Create(FileName: string);
    Destructor Destroy; override;
    procedure Process;
    function ProcessStart: boolean;
    procedure ProcessStop;
    function  ProcessWork(Amount: integer): boolean;
    procedure Start;
    procedure Stop;
    function GetSeqStr: string;
    function GetStateStr: string;
  end;

  function FindMsqGroup(var F: textfile; GroupName: string): boolean;

implementation
uses inifiles, tagstorage, sysutils, dialogs, numbers, classes,
    MesLogging, hyperstr, forms, rpMessages, Graphics, main;


{ TMachSeq }

////////////////////////////////////////////////////////////////////////////////
// СОЗДАНИЕ ПОДГРУППЫ
////////////////////////////////////////////////////////////////////////////////
function FindMsqGroup(var F: textfile; GroupName: string): boolean;
var
  s: string;
  flag: boolean;
begin
  flag := false;
  while not(eof(F)) do begin
    readln(F, s);
    s := UpperCase(trim(s));
    if copy(s, 1, 6) = '#GROUP' then begin
      delete(s, 1, 6);
      if GroupName = trim(s) then begin
        flag := true;
        break;
      end;
    end;
  end;
  result := flag;
end;


procedure TMachSeq.LoadMsqGroup(GroupName: string; var idx: integer);
var
  i: integer;
  F: TextFile;
  sl: TStringList;
  s: string;
  flag: boolean;
begin
  GroupName := UpperCase(copy(GroupName, 2, length(GroupName)-2));
  sl := TStringList.Create;
  sl.Clear;

  AssignFile(F, CurDir + 'MachSeq\'+MachSeq_GroupsFile);
  try
    reset(F);

    // поиск группы
    flag := FindMsqGroup(F, GroupName);

    // считывание содержимого группы
    if flag then
      while not(eof(F)) do begin
        readln(F, s);
        s := trim(s);
        if copy(s,1,1)='#' then break;
        if (s='') or (copy(s,1,2)='//') then continue;
        sl.Add(s);
      end
    else
      s_err := 'Подгруппа ' + GroupName + ' не найдена!';

  except
  end;
  CloseFile(F);

  // создание элементов из считанного содержимого группы
  for i:=0 to sl.Count-1 do begin
    s := sl.Strings[i];
    if (s[1]='[') and (s[length(s)]=']') then
      LoadMsqGroup(s, idx)
    else
      LoadMsqItem(s, idx);
  end;

  sl.Free;
end;



////////////////////////////////////////////////////////////////////////////////
// CONSTRUCTOR
////////////////////////////////////////////////////////////////////////////////
constructor TMachSeq.Create(FileName: string);
var
  F: TextFile;
  s: string;
begin
  inherited Create;
  LoadSuccess := false;
  s_err := '';
  SharedMachs := ';';
  List := nil;

  // TagPath
  TagPath := MachSeq_TagPath;
  TagPath := trim(TagPath);
  if length(TagPath)>0 then TagPath := TagPath + '.';

  // Загрузка маршрута из файла
  try
    AssignFile(F, FileName);
    Reset(F);
    Readln(F, Name);

    // подсчет кол-ва элементов в цепочке
    MaxItemCount := 1024;
    ItemCount := 0;
    SetLength(Items, MaxItemCount);
    while not(eof(F)) do begin
      Readln(F, s);
      s := trim(s);
      if s='' then continue;

      if ItemCount > MaxItemCount then begin
        s_err := 'Превышения допустимой длины цепочки!';
        break;
      end;

      if (s[1]='[') and (s[length(s)]=']') then
        LoadMsqGroup(s, ItemCount)
      else
        LoadMsqItem(s, ItemCount);
    end;
  except
    s_err := 'Ошибка чтения файла цепочки!';
  end;
  CloseFile(F);

  // подготовка SharedMachs
  Translate( SharedMachs, ' ,', ';;');

  // поиск индекса тега звонка
  s := TagPath + MachSeq_BeepStart;
  TagBeepStartIdx := GetTagIndex(s);

  // Вывод сообщения об ошибке
  if not(length(s_err)>0) then LoadSuccess := true;
end;




function TMachSeq.GetSeqStr: string;
begin
  result := '';
end;


function TMachSeq.GetStateStr: string;
var
  s: string;
begin
  s:='';
  case State of
    0: s := 'Выключен';
    1: s := 'Запуск (звонок)';
    2: s := 'Запуск';
    3: s := 'Запуск ' + CurMach;
    4: s := 'Запуск';
    5: s := 'Работает';
    6: s := 'Останов';
    7: s := 'Останов ' + CurMach;
    8: s := 'Останов';
    255: s := 'Авария ' + CurMach;
  end;
  result := s;
end;

////////////////////////////////////////////////////////////////////////////////
// PROCESS
////////////////////////////////////////////////////////////////////////////////
procedure TMachSeq.Process;
begin
  RepeatFlag := false;
  repeat
    case State of

      0:begin  // отключен
        end;

      1:begin  // Запуск (звонок)
        if StateCnt=0 then begin
          SetTagValue(TagBeepStartIdx, 3);
          inc(StateCnt);
        end else if StateCnt>=5 then begin
          SetTagValue(TagBeepStartIdx, 0);
          State := 2;
          StateCnt := 0;
        end else inc(StateCnt);
      end;

      2:begin  // Запуск
        State := 3;
        StateCnt := StartStateCnt;;
        CurMach := '';
        TmpMachIdx := 0;
        TmpMachCnt := 0;
      end;

      3:begin  // Запуск (машины)
        if ProcessWork(StateCnt)then begin

          repeat
            if StateCnt >= ItemCount then break;
          until not ProcessStart;

          if StateCnt >= ItemCount then begin
            State := 4;
            StateCnt := 0;
          end;
        end;
      end;

      4:begin  // Запуск
        State := 5;
      end;

      5:begin  // Работает
        ProcessWork(ItemCount);
      end;

      6:begin  // Останов
//        PutSoundQueue(MachSeq_MarshStopSnd);
        State := 7;
        StateCnt := ItemCount-1;
        CurMach := '';
      end;

      7:begin  // Останов (машины)
        if StateCnt >= 0 then begin
          ProcessStop;
        end else begin
          State := 8;
          StateCnt := 0;
        end;
      end;

      8:begin  // Останов
        State := 0;
      end;

      255:begin  // Авария
        ProcessWork(StateCnt);
      end;

    end;
  until not RepeatFlag;
end;

procedure TMachSeq.Start;
begin
  State := 1;
  StateCnt := 0;
end;

procedure TMachSeq.Stop;
begin
  State := 6;
  StateCnt := 0;
end;



////////////////////////////////////////////////////////////////////////////////
// СОЗДАНИЕ ЭЛЕМЕНТА
////////////////////////////////////////////////////////////////////////////////
procedure TMachSeq.LoadMsqItem(s: string; var idx: integer);
const
  TagNotFound = 'Не найден тег для ';
var
  k,v: integer;
  p: PSeqRec;
begin

  if (uppercase(copy(s,1,7))='.SHARED') or (copy(s,1,6)='.ОБЩИЕ') or (copy(s,1,6)='.общие') then begin
    delete(s, 1, pos('=',s));
    SharedMachs := SharedMachs + trim(s) + ';';
    exit;
  end;

  s := trim(s);
  if (s='') or (copy(s,1,2)='//') then exit;

  p := @Items[idx];

  // определение типа элемента
  if UpperCase(copy(s, 1, 5))='.MACH' then
    p.ItemType := siMACH
  else if (UpperCase(copy(s, 1, 4))='.VAL') or (UpperCase(copy(s, 1, 4))='.ЗНЧ') then
    p.ItemType := siVAL
  else if (UpperCase(copy(s, 1, 5))='.TDAT') or (UpperCase(copy(s, 1, 5))='.ТДАТ') then
    p.ItemType := siTDAT
  else if (UpperCase(copy(s, 1, 6))='.DELAY') or (UpperCase(copy(s, 1, 6))='.ЗАДЕР') then
    p.ItemType := siDEL
  else if (UpperCase(copy(s, 1, 6))='.MSTOP') or (UpperCase(copy(s, 1, 6))='.МСТОП') then
    p.ItemType := siMSTOP
  else if (UpperCase(copy(s, 1, 6))='.MBUTT') or (UpperCase(copy(s, 1, 6))='.МКНОП') then
    p.ItemType := siMBUTT
  else
    p.ItemType := siMACH;

  // получение данных элемента цепочки
  case p.ItemType of

        siMACH: begin
            new(PMachSeqRec(p.Item));
            with PMachSeqRec(p.Item)^ do begin
              if pos(' ',s)>0 then delete(s, 1, pos(' ',s));
              TagName := trim(s);

              s := TagPath + 'MCTL_' + TagName + '_Control';
              TagCtrlIdx := GetTagIndex(s);
              s := TagPath + 'MACH_' + TagName + '_Sost';
              TagSostIdx := GetTagIndex(s);
              s := TagPath + 'MACH_' + TagName + '_MasterOut';
              TagMasterOutIdx := GetTagIndex(s);

              if (TagCtrlIdx<0) or (TagSostIdx<0) or (TagMasterOutIdx<0) then
                s_err := TagNotFound + TagName;
            end;
            inc(idx);
          end;

        siVAL: begin
            new(PValSeqRec(p.Item));
            with PValSeqRec(p.Item)^ do begin
              delete(s, 1, 4);
              k := pos('=', s);
              TagName := trim(copy(s, 1, k-1));
              delete(s, 1, k);
              k := pos('/', s);
              val(copy(s,1,k-1), SetValue, v);
              delete(s, 1, k);
              val(s, ResValue, v);

              s := TagPath + TagName;
              TagIdx := GetTagIndex(s);
              if TagIdx<0 then s_err := TagNotFound + TagName;
            end;
            inc(idx);
          end;

        siTDAT: begin
            new(PTdatSeqRec(p.Item));
            with PTdatSeqRec(p.Item)^ do begin
              delete(s, 1, 5);
              k := pos('=', s);
              TagName := trim(copy(s, 1, k-1));
              delete(s, 1, k);
              val(s, SetValue, v);

              s := TagPath + TagName;
              TagIdx := GetTagIndex(s);
              if TagIdx<0 then s_err := TagNotFound + TagName;
            end;
            inc(idx);
          end;

        siDEL: begin
            new(PDelSeqRec(p.Item));
            with PDelSeqRec(p.Item)^ do begin
              k := pos(' ', s);
              delete(s, 1, k);
              s := trim(s);
              k := pos(' ', s);
              val(copy(s,1,k-1), DelTime, v);
              delete(s, 1, k);
              s := UpperCase(trim(s));
              OnStart := (pos('START', s)>0) or (pos('СТАРТ', s)>0) or (pos('ЗАПУСК', s)>0);
              OnStop := (pos('STOP', s)>0) or (pos('СТОП', s)>0) or (pos('ОСТАНОВ', s)>0);
              Cnt := 0;
            end;
            inc(idx);
          end;

        siMSTOP: begin
            new(PMstopSeqRec(p.Item));
            with PMstopSeqRec(p.Item)^ do begin
              delete(s, 1, 6);
              k := pos('=', s);
              TagName := trim(copy(s, 1, k-1));
              delete(s, 1, k);
              k := pos('/', s);
              val(copy(s,1,k), SetValue, v);
              delete(s, 1, k);
              k := pos(':', s);
              val(copy(s,1,k), DelTime, v);
              delete(s, 1, k);
              MesStr := trim(s);
              Cnt := 0;

              s := TagPath + TagName;
              TagIdx := GetTagIndex(s);
              if TagIdx<0 then s_err := TagNotFound + TagName;
            end;
            inc(idx);
          end;

        siMBUTT: begin
            new(PMbuttSeqRec(p.Item));
            with PMbuttSeqRec(p.Item)^ do begin
              delete(s, 1, 6);
              s := trim(s);
              k := pos(' ', s);
              TagStartName := trim(copy(s, 1, k));
              delete(s, 1, k);
              TagStopName := trim(s);
              Trigger := false;
              StopFlag := false;

              s := TagPath + TagStartName;
              TagStartIdx := GetTagIndex(s);
              s := TagPath + TagStopName;
              TagStopIdx := GetTagIndex(s);

              if (TagStartIdx<0) or (TagStopIdx<0) then
                s_err := TagNotFound + TagStartName + '/' + TagStopName;
            end;
            inc(idx);
          end;

  end;
end;

////////////////////////////////////////////////////////////////////////////////
// DESTRUCTOR
////////////////////////////////////////////////////////////////////////////////
destructor TMachSeq.Destroy;
var
  i: integer;
begin
  for i:=0 to ItemCount-1 do begin
    case Items[i].ItemType of
      siMACH: dispose(PMachSeqRec(Items[i].Item));
      siVAL: dispose(PValSeqRec(Items[i].Item));
      siTDAT: dispose(PTdatSeqRec(Items[i].Item));
      siDEL: dispose(PDelSeqRec(Items[i].Item));
      siMSTOP: dispose(PMstopSeqRec(Items[i].Item));
      siMBUTT: dispose(PMbuttSeqRec(Items[i].Item));
    end;
  end;
  Items:=nil;
  inherited;
end;

////////////////////////////////////////////////////////////////////////////////
// START ITEM
////////////////////////////////////////////////////////////////////////////////
function TMachSeq.ProcessStart: boolean;
var
  i,k, OldStateCnt: integer;
  OnceMore: boolean;
begin
  OldStateCnt := StateCnt;
//  OnceMore := false;

  case Items[StateCnt].ItemType of

    siMACH: with PMachSeqRec(Items[StateCnt].Item)^ do begin
        CurMach := TagName;
        i := GetTagValue(TagCtrlIdx);
        k := GetTagValue(TagSostIdx);

        if k in [4,5] then begin
          if TmpMachIdx = TagCtrlIdx then begin
            if TmpMachCnt > 5 then
              State := 255
            else
              inc(TmpMachCnt);
          end else begin
            SetTagValue(TagCtrlIdx, (i or $10));
            TmpMachIdx := TagCtrlIdx;
            TmpMachCnt := 0;
          end;
        end else begin
          if (i and $0C)<>$0C then
            SetTagValue(TagCtrlIdx, (i or $0C))
          else 
            if GetTagValue(TagMasterOutIdx)=1 then inc(StateCnt);
          TmpMachIdx := TagCtrlIdx;
        end;
      end;

    siVAL: with PValSeqRec(Items[StateCnt].Item)^ do begin
        CurMach := TagName;
        if GetTagValue(TagIdx) <> SetValue then
          SetTagValue(TagIdx, SetValue)
        else
          inc(StateCnt);
      end;

    siTDAT: with PTdatSeqRec(Items[StateCnt].Item)^ do begin
        CurMach := TagName;
        if GetTagValue(TagIdx) = SetValue then begin
          inc(StateCnt);
        end else
          if StateCnt < ItemCount then begin
            k := StateCnt;
            inc(StateCnt);
            ProcessStop;
            StateCnt := k;
          end;

      end;

    siDEL: with PDelSeqRec(Items[StateCnt].Item)^ do begin
        if (not OnStart) or (Cnt>=DelTime) then begin
          CurMach := 'Задержка 0';
          inc(StateCnt);
          Cnt := 0;
        end else begin
          CurMach := 'Задержка ' + IntToStr(DelTime-Cnt);
          inc(Cnt);
        end;
      end;

    siMSTOP: with PMstopSeqRec(Items[StateCnt].Item)^ do begin
        CurMach := TagName;
        if GetTagValue(TagIdx) = SetValue then begin
          Cnt := 0;
          inc(StateCnt);
        end else
          if Cnt > DelTime then begin
            Cnt := 0;
            State := 255;
//            PutSoundQueue(MachSeq_MarshSuspSnd);
            rpCreateMessage('Запуск приостановлен!'#13#13 + 'Маршрут:  ' + Name +
                  #13'Причина:  ' + MesStr);
            SaveMessage(22, Name, MesStr);
          end else begin
            inc(Cnt);
          end;
      end;

    siMBUTT: with PMbuttSeqRec(Items[StateCnt].Item)^ do begin
        CurMach := 'Кнопки Старт/Стоп';

        if Trigger then begin
          if GetTagValue(TagStopIdx) > 0 then begin
            Trigger := false;
            StopFlag := true;
            State := 6;
          end;
        end else begin
          if GetTagValue(TagStartIdx) > 0 then begin
            Trigger := true;
            StopFlag := false;
          end;
        end;

        if Trigger then inc(StateCnt);
      end;

  end;

  // result - once more pass
  result := (OldStateCnt < StateCnt);
end;

////////////////////////////////////////////////////////////////////////////////
// STOP ITEM
////////////////////////////////////////////////////////////////////////////////
procedure TMachSeq.ProcessStop;
begin
  repeat

  case Items[StateCnt].ItemType of

    siMACH: with PMachSeqRec(Items[StateCnt].Item)^ do begin
        if CanStop(TagName) then begin
          CurMach := TagName;
          SetTagValue(TagCtrlIdx, GetTagValue(TagCtrlIdx) and $F3 );
        end;
        dec(StateCnt);
        break;
      end;

    siVAL: with PValSeqRec(Items[StateCnt].Item)^ do begin
        if CanStop(TagName) then begin
          CurMach := TagName;
          SetTagValue(TagIdx, ResValue);
        end;
        dec(StateCnt);
        break;
      end;

    siDEL: with PDelSeqRec(Items[StateCnt].Item)^ do begin
        CurMach := 'Задержка';
        if (not OnStop) or (Cnt>=DelTime) then begin
          CurMach := 'Задержка 0';
          dec(StateCnt);
          Cnt := 0;
        end else begin
          inc(Cnt);
          CurMach := 'Задержка ' + IntToStr(DelTime-Cnt);
        end;
        break;
      end;

    siMBUTT: with PMButtSeqRec(Items[StateCnt].Item)^ do begin
        CurMach := 'Кнопка Старт/Стоп';
        Trigger := false;
        if StopFlag then begin
          State := 3;
          StopFlag := false;
        end else
          dec(StateCnt);
        break;
      end;

    else begin
      CurMach := '';
      dec(StateCnt);
    end;

  end;

  until (StateCnt<0);
end;

////////////////////////////////////////////////////////////////////////////////
// CAN STOP
////////////////////////////////////////////////////////////////////////////////
function TMachSeq.CanStop(s: string): boolean;
var
  i,j: integer;
  D: TMachSeq;
begin
  if List <> nil then begin
    for j:=0 to List.Items.Count-1 do
      if List.Items[j].Data <> Self then begin
        D := TMachSeq(List.Items[j].Data);
        if D.State = 5 then

          for i:=0 to D.ItemCount-1 do begin
            case D.Items[i].ItemType of
              siMACH: with PMachSeqRec(D.Items[i].Item)^ do
                  if (TagName=s) and ((GetTagValue(TagCtrlIdx) and $0C)=$0C) then begin
                    result := false;
                    exit;
                  end;
              siVAL: with PValSeqRec(D.Items[i].Item)^ do
                  if (TagName=s) and (GetTagValue(TagIdx)=SetValue) then begin
                    result := false;
                    exit;
                  end;
            end;
          end;
      end;
  end;
  result := true;
end;

////////////////////////////////////////////////////////////////////////////////
// WORK ITEMS
////////////////////////////////////////////////////////////////////////////////
function TMachSeq.ProcessWork(Amount: integer): boolean;
var
  i: integer;
  StopItem: integer;
  sCurMach: string;
begin
  if Amount<0 then begin result:=false; exit; end;

  StopItem := -1;

  for i:=0 to Amount-1 do begin
    case Items[i].ItemType of
      siMACH: with PMachSeqRec(Items[i].Item)^ do begin
          if not(((GetTagValue(TagCtrlIdx) and $0C)=$0C) and
                (GetTagValue(TagMasterOutIdx)=1)) then begin
            StopItem := i+1;
            sCurMach := TagName;
            SaveMessage(22, Name, TagName);
          end;
        end;

      siVAL: with PValSeqRec(Items[i].Item)^ do begin
          if GetTagValue(TagIdx)<>SetValue then begin
            StopItem := i+1;
            sCurMach := TagName;
            SaveMessage(22, Name, TagName);
          end;
        end;

      siTDAT: with PTdatSeqRec(Items[i].Item)^ do begin
          if (GetTagValue(TagIdx)<>SetValue) and (i+1<Amount) then begin
            StateCnt := i+1;
            ProcessStop;
            State := 3;
          end;
        end;

      siMSTOP: with PMstopSeqRec(Items[i].Item)^ do begin
          if GetTagValue(TagIdx)<>SetValue then begin
            if Cnt > DelTime then begin
              State := 6;
              SaveMessage(16, Name, MesStr);
            end else begin
              if Cnt = 0 then with
                    rpCreateMessage('Маршрут "' + Name + '" будет остановлен ' +
                    'через ' + inttostr(DelTime) + ' секунд.'#13 +
                    'Причина:  ' + MesStr) do begin
                Color := clRed;
                lbMessage.Font.Color := clWhite;
                StartCountDown(DelTime, true);
//                PutSoundQueue(MachSeq_MarshSuspSnd);
              end;
              inc(Cnt);
            end;
          end else begin
              if Cnt > 0 then with
                    rpCreateMessage('Отмена останова маршрута "' + Name + '"'#13 +
                    'Причиной было:  ' + MesStr) do begin
                Color := clLime;
                StartCountDown(DelTime, true);
              end;
            Cnt := 0;
          end;
        end;


      siMBUTT: with PMbuttSeqRec(Items[i].Item)^ do begin
          if Trigger then begin
            if GetTagValue(TagStopIdx) > 0 then begin
              Trigger := false;
              StopFlag := true;
              State := 6;
            end;
          end;
        end;


    end;
    if StopItem>=0 then break;
  end;

  // останов по аварии
  if StopItem >= 0 then begin
    for i:=StopItem to ItemCount-1 do begin
      StateCnt := i;
      ProcessStop;
    end;
    CurMach := sCurMach;
    State := 255;
    StateCnt := StopItem-1;
//    PutSoundQueue(MachSeq_MarshAlarmSnd);
    result := false;
  end else
    result := true;
end;


end.
