unit regpas2jsatom;

{$mode objfpc}{$H+}
interface

uses
  Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs,
  ProjectIntf, LazIDEIntf, LazFileUtils;

type

  { TAtomPackageProjectDescriptor }

  TAtomPackageProjectDescriptor = class(TProjectDescriptor)
  Private
    FPackageClassName,
    FPackageDir,
    FPackageLicense,
    FPackageName,
    FPackageDescription : String;
    FlinkPackage : Boolean;
    FKeyWords,
    FCommands : TStrings;
    FActivationCommands : TStrings;
    FFiles : TStrings;
    procedure AddCSSFile({%H-}aProject: TLazProject);
    procedure AddFileToProject(const aFileName: string);
    procedure AddGlueFile({%H-}aProject: TLazProject);
    procedure AddKeyMapFile({%H-}aProject: TLazProject);
    procedure AddMenuFile({%H-}aProject: TLazProject);
    procedure AddPackageJSONFile({%H-}aProject: TLazProject);
    procedure AddProjectFile(AProject: TLazProject);
    procedure CreateProjectDirs;
    procedure CreateProjectSource(Src: TStrings);
    procedure DoDefaultReplacements(Src: TStrings);
    procedure InitVars;
    procedure InsertHandlerDefinitions(Src: TStrings; aIndex, aIndent: Integer);
    procedure InsertHandlerImplementations(Src: TStrings; aIndex: Integer);
    procedure InsertHandlerRegistrations(Src: TStrings; aIndex: Integer);
    function LoadDefault(Src: TStrings; aFileName: string): boolean;
    Function ShowOptionsDialog : TModalResult;
  public
    constructor Create(); override;
    destructor destroy; override;
    Function DoInitDescriptor : TModalResult; override;
    function GetLocalizedName: string; override;
    function GetLocalizedDescription: string; override;
    function InitProject(AProject: TLazProject) : TModalResult; override;
    function CreateStartFiles({%H-}AProject: TLazProject) : TModalResult; override;
  published
    { Published declarations }
  end;

procedure Register;

implementation

uses
  {$IFDEF UNIX}
  baseunix,
  {$ENDIF}
  fpjson,frmpas2jsatompackagesettings, CompOptsIntf, pjscontroller,
  MenuIntf, pjsprojectoptions, pjsdsgnoptions, strpas2jsdesign;

Var
  AtomProjDesc:TAtomPackageProjectDescriptor;


procedure Register;

begin
  //RegisterIdeMenuCommand(itmOptionsDialogs,STemplateSettings,SProjectTemplateSettings,nil,@ChangeSettings);
  AtomProjDesc:=TAtomPackageProjectDescriptor.Create();
  RegisterProjectDescriptor(AtomProjDesc);
end;


{ TAtomPackageProjectDescriptor }

function TAtomPackageProjectDescriptor.ShowOptionsDialog : TModalResult;

begin
  With TAtomPackageSettingsForm.Create(Application) do
    try
      PkgDescription:=FPackageDescription;
      PkgName:=FPackageName;
      pkgClassName:= FPackageClassName;
      pkgLink:=FlinkPackage;
      PkgCommands:=FCommands;
      PkgActivationCommands:=FActivationCommands;
      PkgLicense:=FPackageLicense;
      PkgKeyWords:=FkeyWords.CommaText;
      PkgDir:=FPackageDir;
      Result:=ShowModal;
      if (Result=mrOK) then
        begin
        FPackageDescription:=PkgDescription;
        FPackageName:=PkgName;
        FlinkPackage:=pkgLink;
        FCommands.Assign(PkgCommands);
        FActivationCommands.Assign(PkgActivationCommands);
        FPackageLicense:=PkgLicense;
        FKeyWords.CommaText:=PkgKeyWords;
        FPackageDir:=IncludeTrailingPathDelimiter(PkgDir);
        FPackageClassName:=pkgClassName;
        end;
    finally
      Free;
    end;
end;


constructor TAtomPackageProjectDescriptor.Create;
begin
  inherited Create;
  FKeyWords:=TStringList.Create;
  FCommands:=TStringList.Create;
  FActivationCommands:=TStringList.Create;
  FFiles:=TStringList.Create;
  InitVars;
  Name:='pas2jsatompackage';
end;

destructor TAtomPackageProjectDescriptor.destroy;
begin
  FreeAndNil(FFiles);
  FreeAndNil(FCommands);
  FreeAndNil(FKeywords);
  FreeAndNil(FActivationCommands);
  Inherited;
end;


function TAtomPackageProjectDescriptor.GetLocalizedName: string;
begin
  Result:=pjsdNewAtomPackage;
end;

function TAtomPackageProjectDescriptor.GetLocalizedDescription: string;
begin
  Result:=pjsdNewAtomPackageDescr;
end;

Procedure TAtomPackageProjectDescriptor.InitVars;

begin
  FPackageDir:=GetUserDir+'github'+pathdelim+'myatompackage';
  FPackageName:='my-atom-package';
  FPackageDescription:='My atom package';
  FPackageClassName:='TMyAtomPackageApplication';
  FPackageLicense:='MIT';
{$IFDEF UNIX}
  FLinkPackage:=True;
{$ELSE}
  FLinkPackage:=False;
{$ENDIF}
  FKeywords.Clear;
  FCommands.Clear;
  FActivationCommands.Clear;
end;

function TAtomPackageProjectDescriptor.DoInitDescriptor: TModalResult;

begin
  InitVars;
  Result:=ShowOptionsDialog;
end;

Procedure TAtomPackageProjectDescriptor.CreateProjectDirs;

Const
  DirCount = 5;
  DefDirs : Array [1..DirCount] of string =
      ('','lib','keymaps','menus','styles');

Var
  S : String;
  {$IFDEF UNIX}
  L : String;
  {$ENDIF}

begin
  FPackageDir:=IncludeLeadingPathDelimiter(FPackageDir);
  For S in DefDirs do
    If not ForceDirectories(FPackageDir+S) then
      ShowMessage('Failed to create directory '+FPackageDir+S);
{$IFDEF UNIX}
  if FLinkPackage then
    begin
    L:=Sysutils.GetUserDir+'.atom'+PathDelim+'packages/'+FPackageName;
    fpSymlink(PChar(FPackageDir),PChar(L));
    end;
{$ENDIF}
end;

{$I atomdefaults.inc}


Procedure TAtomPackageProjectDescriptor.InsertHandlerDefinitions(Src : TStrings; aIndex,aIndent : Integer);

Var
  I,Cnt : Integer;
  Prefix,N,V : String;

  procedure AddLn(aLine : String);

  begin
    Src.Insert(aIndex+Cnt,aLine);
    inc(cnt);
  end;


begin
  cnt:=0;
  Src.Delete(aIndex);
  Prefix:=StringOfChar(' ',aIndent-2); // approximate
  For I:=0 to FCommands.Count-1 do
    begin
    FCommands.GetNameValue(I,N,V);
    AddLn(Prefix+'Procedure '+V+';');
    end;
end;

Procedure TAtomPackageProjectDescriptor.InsertHandlerImplementations(Src : TStrings; aIndex : Integer);

Var
  cnt : Integer;

  procedure AddLn(aLine : String);

  begin
    Src.Insert(aIndex+Cnt,aLine);
    inc(cnt);
  end;
Var
  I : Integer;
  N,V : String;
begin
  cnt:=0;
  Src.Delete(aIndex);
  For I:=0 to FCommands.Count-1 do
    begin
    AddLn('');
    FCommands.GetNameValue(I,N,V);
    AddLn('Procedure '+FPackageClassName+'.'+V+';');
    AddLn('');
    AddLn('begin');
    AddLn('end;');
    AddLn('');
    end;
end;

Procedure TAtomPackageProjectDescriptor.InsertHandlerRegistrations(Src : TStrings; aIndex : Integer);

Var
  cnt : Integer;

  procedure AddLn(aLine : String);

  begin
    Src.Insert(aIndex+Cnt,'  '+aLine);
    inc(cnt);
  end;
Var
  I : Integer;
  N,V : String;

begin
  Src.Delete(aIndex);
  if FCommands.Count=0 then exit;
  AddLn('cmds:=TJSObject.New;');
  For I:=0 to FCommands.Count-1 do
    begin
    FCommands.GetNameValue(I,N,V);
    AddLn('cmds['''+N+''']:=@'+V+';');
    end;
  AddLn('subscriptions.add(atom.commands.add(''workspace'', cmds));');
end;

Procedure TAtomPackageProjectDescriptor.DoDefaultReplacements(Src : TStrings);

Var
  I,P : Integer;

begin
  For I:=Src.Count-1 downto 0 do
    begin
    Src[i]:=StringReplace(Src[I],'%PACKAGENAME%',FPackageName,[rfReplaceALl]);
    Src[i]:=StringReplace(Src[I],'%PACKAGEPROJECTNAME%',StripNonIdentifierChars(FPackageName),[rfReplaceALl]);
    Src[i]:=StringReplace(Src[I],'%CLASSNAME%',FPackageClassName,[rfReplaceALl]);
    P:=pos('%PACKAGEHANDLERINTFS%',Src[i]);
    if P>0 then
      InsertHandlerDefinitions(Src,I,P);
    P:=pos('%PACKAGEHANDLERIMPLS%',Src[i]);
    if P>0 then
      InsertHandlerImplementations(Src,I);
    P:=pos('%PACKAGEHANDLERREGS%',Src[i]);
    if P>0 then
      InsertHandlerRegistrations(Src,I);
    end;
end;

function TAtomPackageProjectDescriptor.LoadDefault(Src : TStrings; aFileName : string) : boolean;

Var
  FN : String;

begin
  Result:=(PJSOptions.AtomTemplateDir<>'');
  if Result then
    begin
    FN:=IncludeTrailingPathDelimiter(PJSOptions.AtomTemplateDir)+aFileName;
    Result:=FileExists(FN);
    if Result then
      Src.LoadFromFile(FN);
    end;
end;

Procedure TAtomPackageProjectDescriptor.AddGlueFile(aProject : TLazProject);

Var
  Src : TStrings;
  FN : String;

begin
  FN:=FPackageDir+'lib'+PathDelim+'packageglue.js';
  Src:=TStringList.Create;
  try
    if not LoadDefault(Src,'glue.js') then
      GetDefaultGlueFile(Src);
    DoDefaultReplaceMents(Src);
    Src.SaveToFile(FN);
  finally
    Src.Free;
  end;
  AddFileToProject(FN);
end;

Procedure TAtomPackageProjectDescriptor.AddCSSFile(aProject : TLazProject);

Var
  Src : TStrings;
  FN : String;

begin
  FN:=FPackageDir+'styles'+PathDelim+'package.less';
  Src:=TStringList.Create;
  try
    if not LoadDefault(Src,'package.less') then
      GetDefaultCSSFile(Src);
    DoDefaultReplaceMents(Src);
    Src.SaveToFile(FN);
  finally
    Src.Free;
  end;
  AddFileToProject(FN);

end;

Procedure TAtomPackageProjectDescriptor.AddKeyMapFile(aProject : TLazProject);

Var
  Src : TStrings;
  FN : String;

begin
  FN:=FPackageDir+'keymaps'+PathDelim+'keymaps.json';
  Src:=TStringList.Create;
  try
    if not LoadDefault(Src,'keymaps.json') then
      GetDefaultKeyMapFile(Src);
    DoDefaultReplaceMents(Src);
    Src.SaveToFile(FN);
  finally
    Src.Free;
  end;
  AddFileToProject(FN);
end;

Procedure TAtomPackageProjectDescriptor.AddMenuFile(aProject : TLazProject);

Var
  Src : TStrings;
  FN : String;

begin
  FN:=FPackageDir+'menus'+PathDelim+'menu.json';
  Src:=TStringList.Create;
  try
    if not LoadDefault(Src,'menu.json') then
      GetDefaultMenuFile(Src);
    DoDefaultReplaceMents(Src);
    Src.SaveToFile(FN);
  finally
    Src.Free;
  end;
  AddFileToProject(FN);
end;

Procedure TAtomPackageProjectDescriptor.AddPackageJSONFile(aProject : TLazProject);

Var
  aJSON,B : TJSONObject;
  keys : TJSONArray;
  S,N,V,FN : String;
  JS : TJSONStringType;
  I : Integer;
  aStream : TStringStream;

begin
  aJSON:=TJSONObject.Create([
   'name',FPackagename,
   'main','lib/packageglue',
   'version','0.0.1',
   'description',FPackageDescription,
   'license',FPackageLicense
  ]);
  try
    Keys:=TJSONArray.Create;
    aJSON.add('keywords',keys);
    For S in FKeyWords do
      Keys.Add(S);
    b:=TJSONObject.Create;
    aJSON.Add('activationCommands',b);
    For I:=0 to FActivationCommands.Count-1 do
      begin
      FActivationCommands.GetNameValue(I,N,V);
      b.Add(V,FPackageName+':'+N);
      end;
    b:=TJSONObject.Create(['atom','>=1.0.0 <2.0.0']);
    aJSON.Add('engines',b);
    b:=TJSONObject.Create([]);
    aJSON.Add('dependencies',b);
    JS:=aJSON.FormatJSON;
    aStream:=TStringStream.Create(JS);
    FN:=FPackageDir+'package.json';
    aStream.SaveToFile(FN);
    addFileToProject(FN);
  finally
    aJSON.Free;
  end;
end;

procedure TAtomPackageProjectDescriptor.CreateProjectSource(Src : TStrings);

begin
  if not LoadDefault(Src,'project.lpr') then
    GetDefaultProjectFile(Src);
  DoDefaultReplaceMents(Src);
end;

procedure TAtomPackageProjectDescriptor.AddProjectFile(AProject: TLazProject);

Var
  aFile : TLazProjectFile;
  FN : String;
  Src : TStrings;

begin
  FN:=FPackageDir+StripNonIdentifierChars(FPackageName)+'.lpr';
  aFile:=aProject.CreateProjectFile(FN);
  AFile.IsPartOfProject:=true;
  AProject.AddFile(AFile,False);
  AProject.MainFileID:=0;
  Src:=TStringList.Create;
  try
    CreateProjectSource(Src);
    Src.SaveToFile(FN);
    AProject.MainFile.SetSourceText(src.Text,true);
  Finally
    Src.Free;
  end;
end;

function TAtomPackageProjectDescriptor.InitProject(AProject: TLazProject) : TModalResult;


Var
  CompOpts : TLazCompilerOptions;
begin
  AProject.Title:=FPackageName;
  AProject.ProjectInfoFile:=FPackageDir+StripNonIdentifierChars(FPackageName)+'.lpi';
  AProject.CustomData.Values[PJSProject]:='1';
  CreateProjectDirs;
  CompOpts:=AProject.LazCompilerOptions;
  SetDefaultNodeJSCompileOptions(CompOpts);
  CompOpts.TargetFilename:='lib/'+StripNonIdentifierChars(FPackageName)+'.js';
  CompOpts.CustomOptions:='-Jiatomimports.js -Jirtl.js -Jc '+CompOpts.CustomOptions+' -Jaatomexports.js';
  SetDefaultNodeRunParams(AProject.RunParameters.GetOrCreate('Default'));
  AddProjectFile(aProject);
  Result:=mrOK;
end;

Procedure TAtomPackageProjectDescriptor.AddFileToProject(Const aFileName : string);

begin
  FFiles.Add(aFileName);
end;

Function TAtomPackageProjectDescriptor.CreateStartFiles(AProject: TLazProject) : TModalresult;

Var
  aFileName : String;

begin
  AddGlueFile(aProject);
  AddCSSFile(aProject);
  AddKeyMapFile(aProject);
  AddMenuFile(aProject);
  AddPackageJSONFile(aProject);
  For aFileName in FFiles do
    LazarusIDE.DoOpenEditorFile(aFileName, -1, -1, [ofProjectLoading,ofQuiet,ofAddToProject]);
  Result:=mrOK;
end;


end.
