unit U_ArchStroyCalc;

interface

uses Forms, StdCtrls, SysUtils, Classes, ComCtrls, Windows, Controls, Contnrs, Messages, Dialogs, Math, TypInfo, RTLConsts,
      DB,kbmMemTable,kbmMemBinaryStreamFormat,kbmMemCSVStreamFormat;

const
  // Object sys names
  osnRoom = 'ÊÎÌÍ';
  osnBrickWall = 'ÊÈÐÑÒÅÍÀ';
  osnWallDivision = 'ÏÅÐÅÃ';
  osnWallVolume = 'ÑÒÅÍ';
  osnBasement = 'ÔÓÍÄ';
  osnBasementColumn = 'ÑÒÎËÁ_ÔÓÍÄÀÌÅÍÒÀ';
  osnFacade = 'ÔÀÑÀÄ';

type
  TASCObject = class;
  TASCObjectList = class;
  TASCBuildingList = class;

  TASCBasic = class(TComponent)
    private
      FCaption: String;
    public
      procedure LoadFromFile(const AFileName: String);
      procedure LoadFromStream(AStream: TStream);
      procedure SaveToFile(const AFileName: String);
      procedure SaveToStream(AStream: TStream);
    published
      property Caption: string read FCaption write FCaption;
  end;

  TASCItem = class(TCollectionItem)
    private
      FCaption: String;
    public
      FSrcObj: TObject;
      constructor Create(Collection: TCollection); override;
    published
      property Caption: string read FCaption write FCaption;
  end;


  // Ïðîåêò/Îðãàíèçàöèÿ
  TASCProject = class(TASCBasic)
    private
      FBuildings: TASCBuildingList;
    public
      constructor Create(AOwner: TComponent); override;
      destructor Destroy; override;
    published
      property Buildings: TASCBuildingList read FBuildings write FBuildings;
  end;

  // Ñòðîéêà
  TASCBuilding = class(TASCItem)
    private
      FObjects: TASCObjectList;
    public
      constructor Create(Collection: TCollection); override;
      destructor Destroy; override;
    published
      property Objects: TASCObjectList read FObjects write FObjects;
  end;
  TASCBuildingList = class(TCollection)
    protected
      function GetItem(Index: Integer): TASCBuilding;
      procedure SetItem(Index: Integer; Value: TASCBuilding);
    public
      property Item[Index: Integer]: TASCBuilding read GetItem write SetItem; default;
      constructor Create;
  end;

  // Îáúåêò (Êîìíàòà)
  TASCObject = class(TASCItem)
    private
      FLength: Double;
      FWidth: Double;
      FHeight: Double;
      FSysName: String;
      FMemTableDump: String;

      FObjects: TASCObjectList;
    public
      constructor Create(Collection: TCollection); override;
      destructor Destroy; override;
      procedure LoadDumpFromMemTable(AMT: TkbmMemTable);
      procedure SaveDumpToMemTable(AMT: TkbmMemTable);
    published
      Property Length: Double read FLength write FLength;
      Property Width: Double read FWidth write FWidth;
      Property Height: Double read FHeight write FHeight;
      Property SysName: String read FSysName write FSysName;
      Property MemTableDump: String read FMemTableDump write FMemTableDump;
      property Objects: TASCObjectList read FObjects write FObjects;
  end;
  TASCObjectList = class(TCollection)
    protected
      function GetItem(Index: Integer): TASCObject;
      procedure SetItem(Index: Integer; Value: TASCObject);
    public
      property Item[Index: Integer]: TASCObject read GetItem write SetItem; default;
      constructor Create;
  end;

  procedure ImportToStroyCalcTest;
  procedure ObjectBinaryToTextView(Input, Output: TStream);

var
  GSFBinary: TkbmBinaryStreamFormat;
  GSFCSV: TkbmCSVStreamFormat;
  GSCDebugMode: Boolean=false;

implementation

{ TASCBasic }

procedure TASCBasic.LoadFromFile(const AFileName: String);
var
  Stream: TFileStream;
begin
  Stream := TFileStream.Create(AFileName, fmOpenRead);
  try
    LoadFromStream(Stream);
  finally
    Stream.Free;
  end;
end;

procedure TASCBasic.LoadFromStream(AStream: TStream);
var
  MemStream: TMemoryStream;
begin
  //AStream.ReadComponent(Self);

  MemStream := TMemoryStream.Create;
  ObjectTextToBinary(AStream, MemStream);
  MemStream.Position := 0;
  MemStream.ReadComponent(Self);
  MemStream.Free;
end;

procedure TASCBasic.SaveToFile(const AFileName: String);
var
  Stream: TFileStream;
begin
  Stream := TFileStream.Create(AFileName, fmCreate);
  try
    SaveToStream(Stream);
  finally
    Stream.Free;
  end;
end;

procedure TASCBasic.SaveToStream(AStream: TStream);
var
  MemStream: TMemoryStream;
begin
  //AStream.WriteComponent(Self);

  MemStream := TMemoryStream.Create;
  MemStream.WriteComponent(Self);
  MemStream.Position := 0;
  if Not GSCDebugMode then
    ObjectBinaryToText(MemStream, AStream)
  else
    ObjectBinaryToTextView(MemStream, AStream);
  MemStream.Free;
end;


constructor TASCItem.Create(Collection: TCollection);
begin
  inherited Create(Collection);
  FSrcObj := nil;
end;

{ TASCBProject }

constructor TASCProject.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FBuildings := TASCBuildingList.Create;
end;

destructor TASCProject.Destroy;
begin
  FBuildings.Free;
  inherited;
end;

{ TASCObjectList }

constructor TASCObjectList.Create;
begin
  inherited Create(TASCObject);
end;

function TASCObjectList.GetItem(Index: Integer): TASCObject;
begin
  Result := TASCObject(inherited GetItem(Index));
end;

procedure TASCObjectList.SetItem(Index: Integer; Value: TASCObject);
begin
  inherited SetItem(Index, Value);
end;

{ TASCBuildingList }

constructor TASCBuildingList.Create;
begin
  inherited Create(TASCBuilding);
end;

function TASCBuildingList.GetItem(Index: Integer): TASCBuilding;
begin
  Result := TASCBuilding(inherited GetItem(Index));
end;

procedure TASCBuildingList.SetItem(Index: Integer; Value: TASCBuilding);
begin
  inherited SetItem(Index, Value);
end;

{ TASCBuilding }

constructor TASCBuilding.Create(Collection: TCollection);
begin
  inherited Create(Collection);
  FObjects := TASCObjectList.Create;
end;

destructor TASCBuilding.Destroy;
begin
  FObjects.Free;
  inherited;
end;

{ TASCObject }

constructor TASCObject.Create(Collection: TCollection);
begin
  inherited Create(Collection);
  FObjects := TASCObjectList.Create;
end;

destructor TASCObject.Destroy;
begin
  FObjects.Free;
  inherited;
end;

procedure TASCObject.LoadDumpFromMemTable(AMT: TkbmMemTable);
var
  StrStream: TStringStream;
begin
  StrStream := TStringStream.Create('');
  if Not GSCDebugMode then
    AMT.SaveToStreamViaFormat(StrStream, GSFBinary)
  else
    AMT.SaveToStreamViaFormat(StrStream, GSFCSV);
  StrStream.Position := 0;
  Self.MemTableDump := StrStream.DataString;
  StrStream.Free;
end;

procedure TASCObject.SaveDumpToMemTable(AMT: TkbmMemTable);
var
  StrStream: TStringStream;
begin
  StrStream := TStringStream.Create(Self.MemTableDump);
  StrStream.Position := 0;
  AMT.LoadFromStreamViaFormat(StrStream, GSFBinary);
  StrStream.Free;
end;

procedure ImportToStroyCalcTest;
var
  ASCProject: TASCProject;
  ASCBuilding: TASCBuilding;
  ASCObject: TASCObject;

  MT: TKbmMemTable;
  sfCSV: TkbmCSVStreamFormat;
  i: Integer;
begin
  try
    ASCProject := TASCProject.Create(nil);
    //ASCProject.Caption;
    ASCProject.LoadFromFile('C:\Temp\stroyCalcProj.scp');
    ASCProject.SaveToFile('C:\Temp\stroyCalcProj_ret.scp');

    if ASCProject.Buildings.Count > 0 then
    begin
      ASCBuilding := ASCProject.Buildings[0];
      //ASCBuilding.Caption
      if ASCBuilding.Objects.Count > 0 then
      begin
        sfCSV := TkbmCSVStreamFormat.Create(nil);
        for i := 0 to ASCBuilding.Objects.Count - 1 do
        begin
          ASCObject := ASCBuilding.Objects[i];
          //ASCObject.Caption
          MT := TKbmMemTable.Create(nil);
          ASCObject.SaveDumpToMemTable(MT);
          MT.SaveToFileViaFormat('C:\Temp\stroyCalcProj_'+IntToStr(i)+'.csv', sfCSV);

          MT.Free;
        end;
        sfCSV.Free;
      end;
    end;

    FreeAndNil(ASCProject);
  except
    on E: Exception do ShowMessage('ImportToStroyCalcTest: '+ E.Message);
  end;
end;

procedure ObjectBinaryToTextView(Input, Output: TStream);
var
  NestingLevel: Integer;
  SaveSeparator: Char;
  Reader: TReader;
  Writer: TWriter;
  ObjectName, PropName: string;

  procedure WriteIndent;
  const
    Blanks: array[0..1] of Char = '  ';
  var
    I: Integer;
  begin
    for I := 1 to NestingLevel do Writer.Write(Blanks, SizeOf(Blanks));
  end;

  procedure WriteStr(const S: string);
  begin
    Writer.Write(S[1], Length(S));
  end;

  procedure NewLine;
  begin
    WriteStr(sLineBreak);
    WriteIndent;
  end;

  procedure ConvertValue; forward;

  procedure ConvertHeader;
  var
    ClassName: string;
    Flags: TFilerFlags;
    Position: Integer;
  begin
    Reader.ReadPrefix(Flags, Position);
    ClassName := Reader.ReadStr;
    ObjectName := Reader.ReadStr;
    WriteIndent;
    if ffInherited in Flags then
      WriteStr('inherited ')
    else if ffInline in Flags then
      WriteStr('inline ')
    else
      WriteStr('object ');
    if ObjectName <> '' then
    begin
      WriteStr(ObjectName);
      WriteStr(': ');
    end;
    WriteStr(ClassName);
    if ffChildPos in Flags then
    begin
      WriteStr(' [');
      WriteStr(IntToStr(Position));
      WriteStr(']');
    end;

    if ObjectName = '' then
      ObjectName := ClassName;  // save for error reporting

    WriteStr(sLineBreak);
  end;

  procedure ConvertBinary;
  const
    BytesPerLine = 32;
  var
    MultiLine: Boolean;
    I: Integer;
    Count: Longint;
    Buffer: array[0..BytesPerLine - 1] of Char;
    Text: array[0..BytesPerLine * 2 - 1] of Char;
  begin
    Reader.ReadValue;
    WriteStr('{');
    Inc(NestingLevel);
    Reader.Read(Count, SizeOf(Count));
    MultiLine := Count >= BytesPerLine;
    while Count > 0 do
    begin
      if MultiLine then NewLine;
      if Count >= 32 then I := 32 else I := Count;
      Reader.Read(Buffer, I);
      BinToHex(Buffer, Text, I);
      Writer.Write(Text, I * 2);
      Dec(Count, I);
    end;
    Dec(NestingLevel);
    WriteStr('}');
  end;

  procedure ConvertProperty; forward;

  procedure ConvertValue;
  const
    LineLength = 64;
  var
    I, J, K, L: Integer;
    S: string;
    W: WideString;
    LineBreak: Boolean;
  begin
    case Reader.NextValue of
      vaList:
        begin
          Reader.ReadValue;
          WriteStr('(');
          Inc(NestingLevel);
          while not Reader.EndOfList do
          begin
            NewLine;
            ConvertValue;
          end;
          Reader.ReadListEnd;
          Dec(NestingLevel);
          WriteStr(')');
        end;
      vaInt8, vaInt16, vaInt32:
        WriteStr(IntToStr(Reader.ReadInteger));
      vaExtended:
        WriteStr(FloatToStr(Reader.ReadFloat));
      vaSingle:
        WriteStr(FloatToStr(Reader.ReadSingle) + 's');
      vaCurrency:
        WriteStr(FloatToStr(Reader.ReadCurrency * 10000) + 'c');
      vaDate:
        WriteStr(FloatToStr(Reader.ReadDate) + 'd');
      vaWString, vaUTF8String:
        begin
          W := Reader.ReadWideString;
          L := Length(W);
          if L = 0 then WriteStr('''''') else
          begin
            I := 1;
            Inc(NestingLevel);
            try
              if L > LineLength then NewLine;
              K := I;
              repeat
                LineBreak := False;
                if (W[I] >= ' ') and (W[I] <> '''') and (Ord(W[i]) <= 127) then
                begin
                  J := I;
                  repeat
                    Inc(I)
                  until (I > L) or (W[I] < ' ') or (W[I] = '''') or
                    ((I - K) >= LineLength) or (Ord(W[i]) > 127);
                  if ((I - K) >= LineLength) then LineBreak := True;
                  WriteStr('''');
                  while J < I do
                  begin
                    WriteStr(Char(W[J]));
                    Inc(J);
                  end;
                  WriteStr('''');
                end else
                begin
                  //WriteStr('#');
                  //WriteStr(IntToStr(Ord(W[I])));
                  WriteStr(W[I]);
                  Inc(I);
                  if ((I - K) >= LineLength) then LineBreak := True;
                end;
                if LineBreak and (I <= L) then
                begin
                  WriteStr(' +');
                  NewLine;
                  K := I;
                end;
              until I > L;
            finally
              Dec(NestingLevel);
            end;
          end;
        end;
      vaString, vaLString:
        begin
          S := Reader.ReadString;
          L := Length(S);
          if L = 0 then WriteStr('''''') else
          begin
            I := 1;
            Inc(NestingLevel);
            try
              if L > LineLength then NewLine;
              K := I;
              repeat
                LineBreak := False;
                if (S[I] >= ' ') and (S[I] <> '''') then
                begin
                  J := I;
                  repeat
                    Inc(I)
                  until (I > L) or (S[I] < ' ') or (S[I] = '''') or
                    ((I - K) >= LineLength);
                  if ((I - K) >= LineLength) then
                  begin
                    LIneBreak := True;
                    if ByteType(S, I) = mbTrailByte then Dec(I);
                  end;
                  WriteStr('''');
                  Writer.Write(S[J], I - J);
                  WriteStr('''');
                end else
                begin
                  //WriteStr('#');
                  //WriteStr(IntToStr(Ord(S[I])));
                  WriteStr(S[I]);
                  Inc(I);
                  if ((I - K) >= LineLength) then LineBreak := True;
                end;
                if LineBreak and (I <= L) then
                begin
                  WriteStr(' +');
                  NewLine;
                  K := I;
                end;
              until I > L;
            finally
              Dec(NestingLevel);
            end;
          end;
        end;
      vaIdent, vaFalse, vaTrue, vaNil, vaNull:
        WriteStr(Reader.ReadIdent);
      vaBinary:
        ConvertBinary;
      vaSet:
        begin
          Reader.ReadValue;
          WriteStr('[');
          I := 0;
          while True do
          begin
            S := Reader.ReadStr;
            if S = '' then Break;
            if I > 0 then WriteStr(', ');
            WriteStr(S);
            Inc(I);
          end;
          WriteStr(']');
        end;
      vaCollection:
        begin
          Reader.ReadValue;
          WriteStr('<');
          Inc(NestingLevel);
          while not Reader.EndOfList do
          begin
            NewLine;
            WriteStr('item');
            if Reader.NextValue in [vaInt8, vaInt16, vaInt32] then
            begin
              WriteStr(' [');
              ConvertValue;
              WriteStr(']');
            end;
            WriteStr(sLineBreak);
            Reader.CheckValue(vaList);
            Inc(NestingLevel);
            while not Reader.EndOfList do ConvertProperty;
            Reader.ReadListEnd;
            Dec(NestingLevel);
            WriteIndent;
            WriteStr('end');
          end;
          Reader.ReadListEnd;
          Dec(NestingLevel);
          WriteStr('>');
        end;
      vaInt64:
        WriteStr(IntToStr(Reader.ReadInt64));
    else
      raise EReadError.CreateResFmt(@sPropertyException,
        [ObjectName, DotSep, PropName, IntToStr(Ord(Reader.NextValue))]);
    end;
  end;

  procedure ConvertProperty;
  begin
    WriteIndent;
    PropName := Reader.ReadStr;  // save for error reporting
    WriteStr(PropName);
    WriteStr(' = ');
    ConvertValue;
    WriteStr(sLineBreak);
  end;

  procedure ConvertObject;
  begin
    ConvertHeader;
    Inc(NestingLevel);
    while not Reader.EndOfList do ConvertProperty;
    Reader.ReadListEnd;
    while not Reader.EndOfList do ConvertObject;
    Reader.ReadListEnd;
    Dec(NestingLevel);
    WriteIndent;
    WriteStr('end' + sLineBreak);
  end;

begin
  NestingLevel := 0;
  Reader := TReader.Create(Input, 4096);
  SaveSeparator := DecimalSeparator;
  DecimalSeparator := '.';
  try
    Writer := TWriter.Create(Output, 4096);
    try
      Reader.ReadSignature;
      ConvertObject;
    finally
      Writer.Free;
    end;
  finally
    DecimalSeparator := SaveSeparator;
    Reader.Free;
  end;
end;

initialization
  //RegisterClass(TASCBuilding);
  GSFBinary := TkbmBinaryStreamFormat.Create(nil);
  GSFCSV := TkbmCSVStreamFormat.Create(nil);

finalization
  FreeAndNil(GSFBinary);
  FreeAndNil(GSFCSV);

end.
