uses
  dstrnum,
  dminiproto,
  SysUtils; // remove it later

const
  CodeGeneratorResponse_error: TFastTag                 = FastTag1;
  CodeGeneratorResponse_file: TFastTag                  = FastTag15;

  CodeGeneratorResponse_File_name: TFastTag             = FastTag1;
  CodeGeneratorResponse_File_insertion_point: TFastTag  = FastTag2;
  CodeGeneratorResponse_File_content: TFastTag          = FastTag15;

var
  ShiftWidth: Integer = 2;

type
TSourceBuilder = object
private
  FIdent: Integer;
  FBuffer: AnsiString;
public
  constructor Init(Ident: Integer = 0);
  procedure Line(const S: AnsiString);
  procedure Append(const S: AnsiString);
  procedure Write(const Source: TSourceBuilder);
  procedure Comment(const S: AnsiString);
  procedure ForgetLastComma; // ^_^
  property Buffer: AnsiString read FBuffer;
end;

TUnitStatus = object
  NestedName: AnsiString;
  UsesList: TSourceBuilder; // uses section
  Declarations: TSourceBuilder; // PFoo = ^TFoo;
  Definitions: TSourceBuilder; // TFoo = object ...
  Fields: TSourceBuilder;
  GettersDeclaration: TSourceBuilder;
  SettersDeclaration: TSourceBuilder;
  PropsDeclaration: TSourceBuilder;
  GettersDefinition: TSourceBuilder;
  SettersDefinition: TSourceBuilder;
  Enums: TSourceBuilder;
  SerializeLocal: TSourceBuilder;
  SerializeBody: TSourceBuilder;
  ParseLocal: TSourceBuilder;
  ParseBody: TSourceBuilder;
  Implement: TSourceBuilder;
  constructor Init(Ident: Integer = 0); overload;
  constructor Init(const Status: TUnitStatus; Ident: Integer = 0); overload;
  procedure Write(const Status: TUnitStatus);
end;

TParser = procedure (var Reader: TMiniprotoParser; var Status: TUnitStatus);

constructor TSourceBuilder.Init(Ident: Integer);
begin
  FIdent := Ident;
  FBuffer := '';
end;

procedure TSourceBuilder.Line(const S: AnsiString);
begin
  FBuffer := FBuffer + Space(FIdent) + S + LineEnding;
end;

procedure TSourceBuilder.Append(const S: AnsiString);
begin
  // ^_^
  SetLength(FBuffer, Length(FBuffer) - Length(LineEnding));
  FBuffer := FBuffer + S + LineEnding;
end;

procedure TSourceBuilder.Write(const Source: TSourceBuilder);
begin
  FBuffer := FBuffer + Source.Buffer;
end;

procedure TSourceBuilder.Comment(const S: AnsiString);
begin
  Line('// ' + S);
end;

procedure TSourceBuilder.ForgetLastComma;
label
  DeleteComma;
var
  I, J: Integer;
begin
  I := Length(FBuffer);
  while I > 0 do
    if FBuffer[I] <> ',' then
      Dec(I)
    else
      goto DeleteComma;
  Exit;

DeleteComma:
  for J := I to Length(FBuffer) - 1 do
    FBuffer[J] := FBuffer[J + 1];
  SetLength(FBuffer, Length(FBuffer) - 1);
end;

constructor TUnitStatus.Init(Ident: Integer = 0);
begin
  UsesList.Init(Ident);
  Declarations.Init(Ident);
  Definitions.Init(Ident);
  Fields.Init(Ident);
  GettersDeclaration.Init(Ident);
  SettersDeclaration.Init(Ident);
  PropsDeclaration.Init(Ident);
  GettersDefinition.Init(Ident);
  SettersDefinition.Init(Ident);
  Enums.Init(Ident);
  SerializeLocal.Init(Ident);
  SerializeBody.Init(Ident);
  ParseLocal.Init(Ident);
  ParseBody.Init(Ident);
  Implement.Init(Ident);
end;

constructor TUnitStatus.Init(const Status: TUnitStatus; Ident: Integer = 0);
begin
  UsesList.Init(Status.UsesList.FIdent + Ident);
  Declarations.Init(Status.Declarations.FIdent + Ident);
  Definitions.Init(Status.Definitions.FIdent + Ident);
  Fields.Init(Status.Fields.FIdent + Ident);
  GettersDeclaration.Init(Status.GettersDeclaration.FIdent + Ident);
  SettersDeclaration.Init(Status.SettersDeclaration.FIdent + Ident);
  PropsDeclaration.Init(Status.PropsDeclaration.FIdent + Ident);
  GettersDefinition.Init(Status.GettersDefinition.FIdent + Ident);
  SettersDefinition.Init(Status.SettersDefinition.FIdent + Ident);
  Enums.Init(Status.Enums.FIdent + Ident);
  SerializeLocal.Init(Status.SerializeLocal.FIdent + Ident);
  SerializeBody.Init(Status.SerializeBody.FIdent + Ident);
  ParseLocal.Init(Status.ParseLocal.FIdent + Ident);
  ParseBody.Init(Status.ParseBody.FIdent + Ident);
  Implement.Init(Status.Implement.FIdent + Ident);
end;

procedure TUnitStatus.Write(const Status: TUnitStatus);
begin
  UsesList.Write(Status.UsesList);
  Declarations.Write(Status.Declarations);
  Definitions.Write(Status.Definitions);
  Fields.Write(Status.Fields);
  GettersDeclaration.Write(Status.GettersDeclaration);
  SettersDeclaration.Write(Status.SettersDeclaration);
  PropsDeclaration.Write(Status.PropsDeclaration);
  GettersDefinition.Write(Status.GettersDefinition);
  SettersDefinition.Write(Status.SettersDefinition);
  Enums.Write(Status.Enums);
  SerializeLocal.Write(Status.SerializeLocal);
  SerializeBody.Write(Status.SerializeBody);
  ParseLocal.Write(Status.ParseLocal);
  ParseBody.Write(Status.ParseBody);
  Implement.Write(Status.Implement);
end;

var
  Index, Wire: Integer;
  Writer: TMiniprotoBuilder;

function SkipDots(const M: AnsiString): AnsiString;
var
  I, Dot: Integer;
begin
  Dot := 0;
  for I := 1 to Length(M) do
    if M[I] = '.' then
      Dot := I;
  Result := Copy(M, Dot + 1, Length(M) - Dot);
end;

function SplitDot(const S: AnsiString): AnsiString;
var
  I: Integer;
begin
  for I := 1 to Length(S) do
    if S[I] = '.' then
      Exit(Copy(S, 1, I - 1));
  Exit(S);
end;

function SafeReservedPascalWord(const Prefix, W: AnsiString): AnsiString; overload;
label
  ReservedWord;
var
  S: AnsiString;
  I: Integer;
begin
  // 1. Skip prefix of _ chars
  I := 1;
  while I <= Length(W) do
    if W[I] = '_' then
      Inc(I)
    else
      Break;

  // 2. Get lowercase and prefix
  if Prefix <> '&' then
    S := Prefix + LowerCase(Copy(W, I, Length(W) - I + 1))
  else
    S := LowerCase(Copy(W, I, Length(W) - I + 1));
    
  // 3. Check for reserved words
  // Turbo Pascal reserved words
  if S = 'type' then goto ReservedWord;
  if S = 'absolute' then goto ReservedWord;
  if S = 'and' then goto ReservedWord;
  if S = 'array' then goto ReservedWord;
  if S = 'asm' then goto ReservedWord;
  if S = 'begin' then goto ReservedWord;
  if S = 'case' then goto ReservedWord;
  if S = 'const' then goto ReservedWord;
  if S = 'constructor' then goto ReservedWord;
  if S = 'destructor' then goto ReservedWord;
  if S = 'div' then goto ReservedWord;
  if S = 'do' then goto ReservedWord;
  if S = 'downto' then goto ReservedWord;
  if S = 'else' then goto ReservedWord;
  if S = 'end' then goto ReservedWord;
  if S = 'file' then goto ReservedWord;
  if S = 'for' then goto ReservedWord;
  if S = 'function' then goto ReservedWord;
  if S = 'goto' then goto ReservedWord;
  if S = 'if' then goto ReservedWord;
  if S = 'implementation' then goto ReservedWord;
  if S = 'in' then goto ReservedWord;
  if S = 'inherited' then goto ReservedWord;
  if S = 'inline' then goto ReservedWord;
  if S = 'interface' then goto ReservedWord;
  if S = 'label' then goto ReservedWord;
  if S = 'mod' then goto ReservedWord;
  if S = 'nil' then goto ReservedWord;
  if S = 'not' then goto ReservedWord;
  if S = 'object' then goto ReservedWord;
  if S = 'of' then goto ReservedWord;
  if S = 'operator' then goto ReservedWord;
  if S = 'or' then goto ReservedWord;
  if S = 'packed' then goto ReservedWord;
  if S = 'procedure' then goto ReservedWord;
  if S = 'program' then goto ReservedWord;
  if S = 'record' then goto ReservedWord;
  if S = 'reintroduce' then goto ReservedWord;
  if S = 'repeat' then goto ReservedWord;
  if S = 'self' then goto ReservedWord;
  if S = 'set' then goto ReservedWord;
  if S = 'shl' then goto ReservedWord;
  if S = 'shr' then goto ReservedWord;
  if S = 'string' then goto ReservedWord;
  if S = 'then' then goto ReservedWord;
  if S = 'to' then goto ReservedWord;
  if S = 'type' then goto ReservedWord;
  if S = 'unit' then goto ReservedWord;
  if S = 'until' then goto ReservedWord;
  if S = 'uses' then goto ReservedWord;
  if S = 'var' then goto ReservedWord;
  if S = 'while' then goto ReservedWord;
  if S = 'with' then goto ReservedWord;
  if S = 'xor' then goto ReservedWord;
  // Free Pascal reserved words
  if S = 'dispose' then goto ReservedWord;
  if S = 'exit' then goto ReservedWord;
  if S = 'false' then goto ReservedWord;
  if S = 'new' then goto ReservedWord;
  if S = 'true' then goto ReservedWord;
  // Object Pascal reserved words
  if S = 'as' then goto ReservedWord;
  if S = 'class' then goto ReservedWord;
  if S = 'dispinterface' then goto ReservedWord;
  if S = 'except' then goto ReservedWord;
  if S = 'exports' then goto ReservedWord;
  if S = 'finalization' then goto ReservedWord;
  if S = 'finally' then goto ReservedWord;
  if S = 'initialization' then goto ReservedWord;
  if S = 'inline' then goto ReservedWord;
  if S = 'is' then goto ReservedWord;
  if S = 'library' then goto ReservedWord;
  if S = 'on' then goto ReservedWord;
  if S = 'out' then goto ReservedWord;
  if S = 'packed' then goto ReservedWord;
  if S = 'property' then goto ReservedWord;
  if S = 'raise' then goto ReservedWord;
  if S = 'resourcestring' then goto ReservedWord;
  if S = 'threadvar' then goto ReservedWord;
  if S = 'try' then goto ReservedWord;
  // Modifiers
  { if S = 'absolute' then goto ReservedWord;
  if S = 'abstract' then goto ReservedWord;
  if S = 'alias' then goto ReservedWord;
  if S = 'assembler' then goto ReservedWord;
  if S = 'bitpacked' then goto ReservedWord;
  if S = 'break' then goto ReservedWord;
  if S = 'cdecl' then goto ReservedWord;
  if S = 'continue' then goto ReservedWord;
  if S = 'cppdecl' then goto ReservedWord;
  if S = 'cvar' then goto ReservedWord;
  if S = 'default' then goto ReservedWord;
  if S = 'deprecated' then goto ReservedWord;
  if S = 'dynamic' then goto ReservedWord;
  if S = 'enumerator' then goto ReservedWord;
  if S = 'experimental' then goto ReservedWord;
  if S = 'export' then goto ReservedWord;
  if S = 'external' then goto ReservedWord;
  if S = 'far' then goto ReservedWord;
  if S = 'far16' then goto ReservedWord;
  if S = 'forward' then goto ReservedWord;
  if S = 'generic' then goto ReservedWord;
  if S = 'helper' then goto ReservedWord;
  if S = 'implements' then goto ReservedWord;
  if S = 'index' then goto ReservedWord;
  if S = 'interrupt' then goto ReservedWord;
  if S = 'iochecks' then goto ReservedWord;
  if S = 'local' then goto ReservedWord;
  if S = 'message' then goto ReservedWord;
  if S = 'name' then goto ReservedWord;
  if S = 'near' then goto ReservedWord;
  if S = 'nodefault' then goto ReservedWord;
  if S = 'noreturn' then goto ReservedWord;
  if S = 'nostackframe' then goto ReservedWord;
  if S = 'oldfpccall' then goto ReservedWord;
  if S = 'otherwise' then goto ReservedWord;
  if S = 'overload' then goto ReservedWord;
  if S = 'override' then goto ReservedWord;
  if S = 'pascal' then goto ReservedWord;
  if S = 'platform' then goto ReservedWord;
  if S = 'private' then goto ReservedWord;
  if S = 'protected' then goto ReservedWord;
  if S = 'public' then goto ReservedWord;
  if S = 'published' then goto ReservedWord;
  if S = 'read' then goto ReservedWord;
  if S = 'register' then goto ReservedWord;
  if S = 'reintroduce' then goto ReservedWord;
  if S = 'result' then goto ReservedWord;
  if S = 'safecall' then goto ReservedWord;
  if S = 'saveregisters' then goto ReservedWord;
  if S = 'softfloat' then goto ReservedWord;
  if S = 'specialize' then goto ReservedWord;
  if S = 'static' then goto ReservedWord;
  if S = 'stdcall' then goto ReservedWord;
  if S = 'stored' then goto ReservedWord;
  if S = 'strict' then goto ReservedWord;
  if S = 'unaligned' then goto ReservedWord;
  if S = 'unimplemented' then goto ReservedWord;
  if S = 'varargs' then goto ReservedWord;
  if S = 'virtual' then goto ReservedWord;
  if S = 'write' then goto ReservedWord; }

  // 4. Return result
  if Prefix <> '&' then
    Exit(Prefix + W)
  else
    Exit(W);

ReservedWord:
  if Prefix <> '&' then
    Exit(Prefix + '_' + W)
  else
    Exit('&' + W);
end;

function SafeReservedPascalWord(const W: AnsiString): AnsiString; inline; overload;
begin
  Result := SafeReservedPascalWord('', W);
end;

procedure TranslateMessageNameToPascal(
  const M: AnsiString;
  out C, O, P: AnsiString); inline;
var
  Temp: AnsiString;
begin
  Temp := SkipDots(M);
  Temp := SafeReservedPascalWord(Temp);
  C := 'T' + Temp;
  O := 'O' + Temp;
  P := 'P' + Temp;
end;

procedure TranslateFieldToPascal(const R: AnsiString; out F, S, G: AnsiString);
begin
  F := SafeReservedPascalWord('F', R);
  S := SafeReservedPascalWord('Set', R);
  G := SafeReservedPascalWord('Get', R);
end;

function TranslateEnumValueToPascal(const EnumName: AnsiString): AnsiString; inline;
begin
  Result := SafeReservedPascalWord(EnumName);
end;

function TranslateEnumTypeToPascal(const EnumName: AnsiString): AnsiString; inline;
begin
  Result := SafeReservedPascalWord('_', SkipDots(EnumName));
end;

function TranslateTypeToPascal(_Type: Integer; Name: AnsiString; out ProtoType: AnsiString; out Conversation: AnsiString; out Wire: Integer): AnsiString;
var
  C, O, P: AnsiString;
begin
  Conversation := '';
  case _Type of
     1{Double}:   begin Result := 'Double';  ProtoType := 'Double'; Wire := WIRE_64BIT; end;
     2{Float}:    begin Result := 'Single';  ProtoType := 'Float'; Wire := WIRE_32BIT; end;
     3{Int64}:    begin Result := 'Int64';   ProtoType := 'Int64'; Wire := WIRE_VARINT; end;
     4{UInt64}:   begin Result := 'QWord';   ProtoType := 'UInt64'; Wire := WIRE_VARINT; end;
     5{Int32}:    begin Result := 'LongInt'; ProtoType := 'Int32'; Wire := WIRE_VARINT; end;
     6{Fixed64}:  begin Result := 'Fixed64'; ProtoType := 'Fixed64'; Wire := WIRE_64BIT; end;// TODO
     7{Fixed32}:  begin Result := 'Fixed32'; ProtoType := 'Fixed32'; Wire := WIRE_32BIT; end;// TODO
     8{Bool}:     begin Result := 'Boolean'; ProtoType := 'Bool'; Wire := WIRE_VARINT; end;
     9{string}:   begin Result := 'AnsiString'; ProtoType := 'string'; Wire := WIRE_LENGTH_DELIMITED; end;
    //10{Group}:    Result := ''; // deprecated?
    11{Message}: begin
        TranslateMessageNameToPascal(Name, C, O, P);
        Result := P;
        ProtoType := ''; // Magic! I hope comparasion with '' is faster.
        Wire := WIRE_LENGTH_DELIMITED;
      end;
    12{Bytes}:    begin Result := 'TBytes'; ProtoType := 'Bytes'; Wire := WIRE_LENGTH_DELIMITED; end;
    13{UInt32}:   begin Result := 'Cardinal'; ProtoType := 'UInt32'; Wire := WIRE_VARINT; end;
    14{Enum}:     begin Result := TranslateEnumTypeToPascal(Name); ProtoType := 'Enum'; Conversation := 'Byte'; Wire := WIRE_VARINT; end;
    15{SFixed32}: begin Result := 'SFixed32'; ProtoType := 'SFixed32'; Wire := WIRE_32BIT; end;// TODO
    16{SFixed64}: begin Result := 'SFixed64'; ProtoType := 'SFixed64'; Wire := WIRE_64BIT; end; // TODO
    17{SInt32}:   begin Result := 'LongInt';  ProtoType := 'SInt32'; Wire := WIRE_VARINT; end;
    18{SInt64}:   begin Result := 'QWord';    ProtoType := 'SInt64'; Wire := WIRE_VARINT; end;
  else
    Writeln(stderr, 'Unknown field type: ', _Type);
    Result := 'Pointer';
  end;
end;

procedure ParseSubMessage(var Reader: TMiniprotoParser; var Status: TUnitStatus; Parser: TParser);
var
  Sub: TMiniprotoParser;
begin
  Sub.Init(Reader.ReadVarint, Reader.Buffer);
  Parser(Sub, Status);
  Sub.Done;
end;

procedure ExtensionRange(var Reader: TMiniprotoParser; var Status: TUnitStatus);
begin
  while Reader.ReadHeader(Index, Wire) do begin
    case Index of
      1{Start}: Reader.ReadVarint;//Writeln(stderr,'    Start: ', Reader.ReadVarint);
      2{End}:  Reader.ReadVarint;//Writeln(stderr,'    End:   ', Reader.ReadVarint);
    else
      Reader.Skip(Wire);
    end;
  end;
end;

procedure FieldDescriptorProto(var Reader: TMiniprotoParser; var Status: TUnitStatus);
var
  Name, TypeName, Extendee, DefaultValue, ProtoType, Conversation: AnsiString;
  Number, _Label, _Type, Wire, Header: Integer;
  F, S, G, PascalType: AnsiString;
begin
  while Reader.ReadHeader(Index, Wire) do begin
    case Index of
      1{Name?}: Name := Reader.ReadString;
      3{Number?}: Number := Reader.ReadVarint;
      4{Label?}: _Label := Reader.ReadVarint;
      5{Type?}: _Type := Reader.ReadVarint;
      6{TypeName?}: TypeName := Reader.ReadString;
      2{Extendee?}: Extendee := Reader.ReadString;
      7{DefaultValue?}: DefaultValue := Reader.ReadString;
      //8{Options?}
    else
      Reader.Skip(Wire);
    end;
  end;
  TranslateFieldToPascal(Name, F, S, G);
  PascalType := TranslateTypeToPascal(_Type, TypeName, ProtoType, Conversation, Wire);
  Header := Wire + (Number shl 3);
  case _Label of 
    1{optional}:
      begin
        Status.Fields.Line('  ' + F + 'Has: Boolean;');
        Status.Fields.Line('  ' + F + ': ' + PascalType + ';');
        Status.SettersDeclaration.Line(
          '  procedure ' + S + '(const _' + Name + ': ' + PascalType + ');');
        Status.SettersDeclaration.Line(
          '  function  ' + G + ': ' + PascalType + ';');
        Status.PropsDeclaration.Line('  property ' + SafeReservedPascalWord('&', Name) + ': ' + PascalType +
          ' read ' + G + ' write ' + S + ';');
        Status.Implement.Line(
          'procedure ' + Status.NestedName + S +
                               '(const _' + Name + ': ' + PascalType + ');');
        Status.Implement.Line('begin');
        Status.Implement.Line('  ' + F + ' := ' + '_' + Name + ';');
        Status.Implement.Line('  ' + F + 'Has := True;');
        Status.Implement.Line('end;');
        Status.Implement.Line('');
        Status.Implement.Line(
          'function  ' + Status.NestedName + G + ': ' + PascalType + ';');
        Status.Implement.Line('begin');
        Status.Implement.Line('  // TODO: maybe checking for Has here?');
        Status.Implement.Line('  Result := ' + F + ';');
        Status.Implement.Line('end;');
        if ProtoType <> '' then
          Status.SerializeBody.Line('  if ' + F + 'Has then TProtobuf.Serialize' + ProtoType + '(Func, ' + IntToStr(Header) + ', ' + Conversation  + '(' + F + '));')
        else
          Status.SerializeBody.Line('  if ' + F + 'Has then ' + F + '^.Serialize(Func);');
        if ProtoType <> '' then
          Status.ParseBody.Line('  {' + IntToStr(Number) + ':} ' + 'TProtobuf.Parse' + ProtoType + '(Func, ' + Conversation + '(' + F + '));')
        else
          Status.ParseBody.Line('  {' + IntToStr(Number) + ':} ' + F + '^.Parse(Func);');
      end;
    2{required}:
      begin
        Status.Fields.Line('  ' + F + ': ' + PascalType + ';');
        Status.SettersDeclaration.Line(
          '  procedure ' + S + '(const _' + Name + ': ' + PascalType + ');');
        Status.Implement.Line(
          'procedure ' + Status.NestedName + S +
                               '(const _' + Name + ': ' + PascalType + ');');
        Status.Implement.Line('begin');
        Status.Implement.Line('  ' + F + ' := ' + '_' + Name + ';');
        Status.Implement.Line('end;');
        if ProtoType <> '' then
          Status.SerializeBody.Line('  TProtobuf.Serialize' + ProtoType + '(Func, ' + IntToStr(Header) + ', ' + F + ');')
        else
          Status.SerializeBody.Line('  ' + F + '^.Serialize(Func);');
      end;
    3{repeated}:
      begin
        if ProtoType <> '' then begin
          Status.Fields.Line('  ' + F + ': TRepeated' + PascalType + ';')
        end else
          Status.Fields.Line('  ' + F + ': TRepeated' + Copy(PascalType, 2, Length(PascalType) - 1) + ';');
      end;
  else
    // something is going wrong...
  end;
end;

procedure MessageOptions(var Reader: TMiniprotoParser; var Status: TUnitStatus);
begin
  while Reader.ReadHeader(Index, Wire) do begin
    case Index of
      1{MessageSetWireFormat}: Writeln(stderr,'      MessageSetWireFormat: ', Reader.ReadVarint <> 0);
      2{NoStandartDescriptorAccessor}: Writeln(stderr,'      NoStandartDescriptorAccessor: ', Reader.ReadVarint <> 0);
      //999{UninterpretedOption}
    else
      Reader.Skip(Wire);
    end;
  end;
end;

procedure Location(var Reader: TMiniprotoParser; var Status: TUnitStatus);
var
  Sub: TMiniprotoParser;
begin
  while Reader.ReadHeader(Index, Wire) do begin
    case Index of
      1{Path}:
        begin
          Sub.Init(Reader.ReadVarint, Reader.Buffer);
          Write(stderr, '      Path: ');
          while not Sub.EndOfMessage do
            Write(stderr, Sub.ReadVarint, ', ');
          Writeln(stderr);
          Sub.Done;
        end;
      2{Span}:
        begin
          Sub.Init(Reader.ReadVarint, Reader.Buffer);
          Write(stderr, '      Span: ');
          while not Sub.EndOfMessage do
            Write(stderr, Sub.ReadVarint, ', ');
          Writeln(stderr);
          Sub.Done;
        end;
      3{LeadingComments}: Writeln(stderr, '      Leading: ', Reader.ReadString);
      4{TrailingComments}: Writeln(stderr, '      Trailing: ', Reader.ReadString);
    else
      Reader.Skip(Wire);
    end;
  end;
end;

procedure SourceCodeInfo(var Reader: TMiniprotoParser; var Status: TUnitStatus);
begin
  while Reader.ReadHeader(Index, Wire) do begin
    case Index of
      1 {Location}: ParseSubMessage(Reader, Status, @Location);
    else
      Reader.Skip(Wire);
    end;
  end;
end;

procedure EnumValueOptions(var Reader: TMiniprotoParser; var Status: TUnitStatus);
begin
  while Reader.ReadHeader(Index, Wire) do
    case Index of
      999{UninterpretedOption}:
    else
      Reader.Skip(Wire);
    end;
end;

procedure EnumValueDescriptorProto(var Reader: TMiniprotoParser; var Status: TUnitStatus);
var
  Name: AnsiString;
  Number: Integer;
begin
  while Reader.ReadHeader(Index, Wire) do
    case Index of
      1{[Name]}: Name := Reader.ReadString;
      2{[Number]}: Number := Reader.DebugReadVarint;
      3{Options}: ParseSubMessage(Reader, Status, @EnumValueOptions);
    else
      Reader.Skip(Wire);
    end;
  Name := TranslateEnumValueToPascal(Name);
  Status.Enums.Line(Name + ' = ' + IntToStr(Number) + ',');
end;

procedure EnumOptions(var Reader: TMiniprotoParser; var Status: TUnitStatus);
begin
  while Reader.ReadHeader(Index, Wire) do
    case Index of
      2{AllowAlias}: Writeln(stderr, '      AllowAlias: ', Reader.ReadVarint <> 0);
      // 999{UninterpretedOption}: should not be here
    else
      Reader.Skip(Wire);
    end;
end;

procedure EnumDescriptorProto(var Reader: TMiniprotoParser; var Status: TUnitStatus);
var
  Name: AnsiString; 
  Values: TUnitStatus;
begin
  Name := '';
  Values.Init(Status, ShiftWidth);
  while Reader.ReadHeader(Index, Wire) do
    case Index of
      1{Name*}: Name := Reader.ReadString;
      2{Value}: ParseSubMessage(Reader, Values, @EnumValueDescriptorProto);
      3{Options}: ParseSubMessage(Reader, Status, @EnumOptions);
    else
      Reader.Skip(Wire);
    end;
  Values.Enums.ForgetLastComma;
  Name := TranslateEnumTypeToPascal(Name);
  Status.Enums.Line(Name + ' = (');
  Status.Write(Values);
  Status.Enums.Line(');');
  Status.Enums.Line('');
end;

procedure MethodOptions(var Reader: TMiniprotoParser; var Status: TUnitStatus);
begin
  while Reader.ReadHeader(Index, Wire) do
    case Index of
      999{UninterpretedOption}:
    else
      Reader.Skip(Wire);
    end;
end;

procedure MethodDescriptorProto(var Reader: TMiniprotoParser; var Status: TUnitStatus);
begin
  while Reader.ReadHeader(Index, Wire) do
    case Index of
      1{Name}: Writeln(stderr, '    Name: ', Reader.ReadString);
      2{InputType}: Writeln(stderr, '    InputType: ', Reader.ReadString);
      3{OutputType}: Writeln(stderr, '    OutputType: ', Reader.ReadString); 
      4{Options}: ParseSubMessage(Reader, Status, @MethodOptions);
    else
      Reader.Skip(Wire);
    end;
end;        

procedure ServiceOptions(var Reader: TMiniprotoParser; var Status: TUnitStatus);
begin
  while Reader.ReadHeader(Index, Wire) do
    case Index of
      999{UninterpretedOption}:
    else
      Reader.Skip(Wire);
    end;
end;

procedure ServiceDescriptorProto(var Reader: TMiniprotoParser; var Status: TUnitStatus);
begin
  while Reader.ReadHeader(Index, Wire) do
    case Index of
      1{Name}: Writeln(stderr, '    Name: ', Reader.ReadString);
      2{Method}: ParseSubMessage(Reader, Status, @MethodDescriptorProto);
      3{Options}: ParseSubMessage(Reader, Status, @ServiceOptions);
    else
      Reader.Skip(Wire);
    end;
end;        

procedure DescriptorProto(var Reader: TMiniprotoParser; var Status: TUnitStatus);
var
  Name, C, O, P: AnsiString;
  Inside: TUnitStatus;
begin
  Inside.Init(Status, ShiftWidth);
  while Reader.ReadHeader(Index, Wire) do begin
    case Index of
      1{Name}:
        begin
          // euristic: NestedType appears after Name
          Name := Reader.ReadString;
          TranslateMessageNameToPascal(Name, C, O, P);
          Inside.NestedName := Status.NestedName + C + '.';
        end;
      2{Field}: ParseSubMessage(Reader, Inside, @FieldDescriptorProto);
      6{Extension}: ParseSubMessage(Reader, Status, @FieldDescriptorProto);
      3{NestedType}: ParseSubMessage(Reader, Inside, @DescriptorProto);
      4{EnumType}: ParseSubMessage(Reader, Inside, @EnumDescriptorProto);
      5{ExtensionRange}: ParseSubMessage(Reader, Status, @ExtensionRange);
      //7{Options}:
    else
      Reader.Skip(Wire);
    end;
  end;
  Status.Declarations.Line(P + ' = ^' + C + ';');
  //Status.Declarations.Line('TArrayOf' + SafeReservedPascalWord(Name) + ' = array of ' + C + ';');
  Status.Declarations.Line('TRepeated' + SafeReservedPascalWord(Name)  + ' = {specialize} TRepeated<' + P + '>;');
  //Status.Declarations.Line('PNode' + SafeReservedPascalWord(Name)  + ' = ^TNode' + SafeReservedPascalWord(Name) + ';');
  //Status.Declarations.Line('PRepeated' + SafeReservedPascalWord(Name)  + ' = ^TRepeated' + SafeReservedPascalWord(Name) + ';');
  //Status.Declarations.Line('TRepeated' + SafeReservedPascalWord(Name)  + ' = object');
  //Status.Declarations.Line('private');
  //Status.Declarations.Line('  FFirst, FLast: PNode' + SafeReservedPascalWord(Name)  + ';');
  //Status.Declarations.Line('end;');

  Status.Definitions.Line(C + ' = object(TProtoMessageObject)');
  Status.Definitions.Line('public type');
  Status.Definitions.Write(Inside.Enums);
  Status.Definitions.Line('');
  Status.Definitions.Write(Inside.Declarations);
  Status.Definitions.Line('');
  Status.Definitions.Write(Inside.Definitions);
  Status.Definitions.Line('');
  Status.Definitions.Line('private');
  Status.Definitions.Write(Inside.Fields);
  Status.Definitions.Line('');
  Status.Definitions.Line('public');
  Status.Definitions.Line('  procedure Serialize(Func: TSerializeFunc); virtual;');
  Status.Definitions.Line('  procedure Parse(Func: TParseFunc); virtual;');
  Status.Definitions.Write(Inside.GettersDeclaration);
  Status.Definitions.Write(Inside.SettersDeclaration);
  Status.Definitions.Write(Inside.PropsDeclaration);
  Status.Definitions.Line('end;');
  Status.Definitions.Line('');
  {Status.Definitions.Line('TNode' + SafeReservedPascalWord(Name)  + ' = object');
  Status.Definitions.Line('  Next: ' + 'PNode' + SafeReservedPascalWord(Name) + ';');
  Status.Definitions.Line('  Value: ' + C + ';');
  Status.Definitions.Line('end;');
  Status.Definitions.Line('');}

  Status.Implement.Write(Inside.Implement);

  Status.Implement.Line('');
  Status.Implement.Line('procedure ' + Inside.NestedName + 'Serialize(Func: TSerializeFunc);');
  Status.Implement.Write(Inside.SerializeLocal);
  Status.Implement.Line('begin');
  Status.Implement.Write(Inside.SerializeBody);
  Status.Implement.Line('end;');

  Status.Implement.Line('');
  Status.Implement.Line('procedure ' + Inside.NestedName + 'Parse(Func: TParseFunc);');
  Status.Implement.Write(Inside.ParseLocal);
  Status.Implement.Line('begin');
  Status.Implement.Write(Inside.ParseBody);
  Status.Implement.Line('end;');
end;

function RemoveFileExt(const FileName: AnsiString): AnsiString;
var
  L: Integer;
begin
  L := Length(FileName);
  while L > 0 do
    if FileName[L] <> '.' then
      Dec(L)
    else
      Exit(Copy(FileName, 1, L - 1));
  Result := FileName;
end;

function GetOutName(const ProtoName: AnsiString): AnsiString;
begin
  Result := RemoveFileExt(ExtractFileName(ProtoName));
end;

function GetUnitName(const OutName: AnsiString): AnsiString;
begin
  Result := OutName + '_pb'; // TODO more general computing
end;

procedure FileDescriptorProto(var Reader: TMiniprotoParser; var _Status: TUnitStatus);
var
  FileName, OutName, OutExt, Namespace: AnsiString;
  I, NamespaceDepth: Integer;
  Status: TUnitStatus;
  Source: TSourceBuilder;
  F: TMiniprotoBuilder;
begin
  NamespaceDepth := 0;
  Status.Init(2);
  while Reader.ReadHeader(Index, Wire) do begin
    case Index of
      1 {[Name]}: begin
          // euristic: NestedType appears after Name
          FileName := Reader.ReadString;
          OutName := GetOutName(FileName);
          Status.NestedName := '';//namespace: OutName + '.';
        end;
      2 {[Package]}: begin
          Namespace := Reader.ReadString;
          NamespaceDepth := 1;
          for I := 1 to Length(Namespace) do
            Inc(NamespaceDepth, Ord(Namespace[I] = '.'));
        end;
      3 {Dependency*}: Status.UsesList.Line(GetUnitName(GetOutName(Reader.ReadString)) +',');
      10{PublicDependency*}: Writeln(stderr, '  PublicDependency: ', Reader.ReadVarint);
      11{WeakDependency*}: Writeln(stderr, '  WeakDependency: ', Reader.ReadVarint);
      4{MessageType*}: ParseSubMessage(Reader, Status, @DescriptorProto);
      5{EnumType*}: ParseSubMessage(Reader, Status, @EnumDescriptorProto);
      6{Service*}: ParseSubMessage(Reader, Status, @ServiceDescriptorProto);
      7{Extension*}: ParseSubMessage(Reader, Status, @FieldDescriptorProto);
      //8 {Options}: ParseSubMessage(Reader, Status, @FieldOptions);
      9 {[SourceCodeInfo]}: ParseSubMessage(Reader, Status, @SourceCodeInfo);
    else
      Reader.Skip(Wire);
    end;
  end;
  OutExt := '_pb.pas';
  Source.Init;
  Source.Comment('Generated by the protocol buffer compiler.  DO NOT EDIT!');
  Source.Comment('source: ' + FileName);
  Source.Line('unit ' + GetUnitName(OutName) + ';');
  Source.Line('');
  Source.Line('interface');
  Source.Line('');
  Source.Line('uses');
  Source.Write(Status.UsesList);
  Source.Line('  protobuf;');
  Source.Line('type');
  //namespace: Source.Line(SplitDot(GetUnitName(OutName)) + ' = object');
  //namespace: Source.Line('public type');
  Source.Write(Status.Enums);
  Source.Line('');
  Source.Write(Status.Declarations);
  Source.Line('');
  Source.Write(Status.Definitions);
  Source.Line('');
  //namespace: Source.Line('private');
  Source.Write(Status.Fields);
  //namespace: Source.Line('public');
  Source.Write(Status.GettersDefinition);
  Source.Write(Status.SettersDeclaration);
  //namespace: Source.Line('end;');
  Source.Line('');
  Source.Line('implementation');
  Source.Write(Status.Implement);
  Source.Line('');
  Source.Line('end.');
  F.Init;
  F.Write(CodeGeneratorResponse_File_name, OutName + OutExt);
  F.Write(CodeGeneratorResponse_File_content, Source.Buffer);
  Writer.Write(CodeGeneratorResponse_file, F);
  F.Done;
end;

procedure CodeGeneratorRequest(var Reader: TMiniprotoParser; var Status: TUnitStatus);
begin
  while Reader.ReadHeader(Index, Wire) do
    case Index of
      1 {FileToGenerate*}: Writeln(stderr, 'FileToGenerate: ' + Reader.ReadString);
      2 {[Parameter]}: Writeln(stderr, 'Parameter: ', Reader.ReadString);
      15 {ProtoFile*}: ParseSubMessage(Reader, Status, @FileDescriptorProto);
    else
      Reader.Skip(Wire);
    end;
end;

var
  Reader: TMiniprotoParser;
  F: TMiniprotoBuilder;
  C: Char;
  I: Integer;
  Status: TUnitStatus;

begin
  Write(stderr, 'Params: ');
  for I := 0 to ParamCount do
    Write(stderr, ParamStr(I));
  Writeln(stderr);
  Reader.Init;

  Writer.Init;

  Status.Init;
  //Writer.Write(CodeGeneratorResponse_error, 'underdeveloped feature');
  CodeGeneratorRequest(Reader, Status);

  Writer.Buffer.FlushToStdout;
  Writer.Done;

  Reader.Done;
end.
