{$MODE FPC}
{$MODESWITCH DEFAULTPARAMETERS}
{$MODESWITCH OUT}
{$MODESWITCH RESULT}
// TODO move unit to opengl profile and remove it
{$define DENG_USE_DRAW_OPENGL}
unit DDrawTexDraft;

interface

uses
  {$IFDEF DENG_USE_DRAW_OPENGL}{$IFDEF ANDROID}GLES,{$ELSE}GL, GLU,{$ENDIF}{$ENDIF}
  dstrnum,
  dvector2f,
  dstream,
  dstreamfile,
  dscreenbuffer,
  dtexture2d,
  deng,
  dresourcemanager;

type
PDrawTexParameters = ^TDrawTexParameters;
TDrawTexParameters = object
public const
  FLAG_MIPMAP       = 1 shl 0; // autogenerate mipmap levels
  FLAG_HOLD_DATA    = 1 shl 1; // holds texture data
  FLAG_MEMORY       = 1 shl 2; // loads directly from memory
  FLAG_DEFAULT      = 1 shl 3;
private
  FEng: TEngine;
  FFlags: Cardinal;
  FTexture: TTexture2dRgba;
  FScreenBuffer: PScreenBuffer;
  FName: AnsiString; // Name for Id
  FFileName: AnsiString; // If it reads from file
  procedure InitDefault;
      // Default texture (white square).
  procedure InitFilename(const FileName: AnsiString; Flags: Cardinal = FLAG_MIPMAP);
      // Initializes loading texture from a file.
  function MakeId: AnsiString;
      // Builds an Id for the parameters.
end;

PDrawTexType = ^TDrawTexType;
TDrawTexType = object(TResourceType)
public
  function GetSingleton: PDrawTexType; static; inline;
  function OnAllocHolder(ResourceRecord: PResourceRecord;
                         Parameters: Pointer;
                         out Holder: Pointer): Boolean; virtual;
  function OnAllocDefaultHolder(ResourceRecord: PResourceRecord;
                         out Holder: Pointer): Boolean; virtual;
  function OnAllocHotSwapingHolder(ResourceRecord: PResourceRecord;
                                   Original: Pointer;
                                   out Holder: Pointer): Boolean; virtual;
  function OnThreadedLoad(Holder: Pointer): Boolean; virtual;
  function OnLoad(Holder: Pointer): Boolean; virtual;
  function OnUnload(Holder: Pointer): Boolean; virtual;
  function OnThreadedUnload(Holder: Pointer): Boolean; virtual;
  function OnDeallocHolder(Holder: Pointer): Boolean; virtual;
  function OnHotSwap(Old, New: PResourceRecord): Boolean; virtual;
end;

PDrawTexHolder = ^TDrawTexHolder;
TDrawTexHolder = object
  {$IFDEF DENG_USE_DRAW_OPENGL}Id: GLuint;{$ENDIF}
  FParameters: TDrawTexParameters;
  Width: LongInt;
  Height: LongInt;
  FRecord: PResourceRecord;
  property GetRecord: PResourceRecord read FRecord;
end;

PDrawTex = ^TDrawTex;
TDrawTex = object(specialize TResource<TDrawTexType, TDrawTexHolder>)
public
  procedure Init(const FileName: AnsiString);
  function  Load(const FileName: AnsiString): Boolean;
  procedure Bind; inline;
  procedure Unbind; inline;
// Draw methods
  procedure Draw(const P, S: TVec2f; FlipX: Boolean = False; FlipY: Boolean = False);
end;

implementation

const
  DEFAULT_TEXTURE: array[0 .. 15] of UInt32 = (
  // $ffffffff, $ffffffff, $ffffffff, $ffffffff,
  // $ffffffff, $ffffffff, $ffffffff, $ffffffff,
  // $ffffffff, $ffffffff, $ffffffff, $ffffffff,
  // $ffffffff, $ffffffff, $ffffffff, $ffffffff
    $ff00ffff, $00ffffff, $ffff00ff, $ffffff00,
    $ff00ffff, $00ffffff, $ffff00ff, $ffffff00,
    $ff00ffff, $00ffffff, $ffff00ff, $ffffff00,
    $ff00ffff, $00ffffff, $ffff00ff, $ffffff00
  );

var
  DrawTexType: TDrawTexType;

procedure TDrawTexParameters.InitDefault;
var
  I: LongInt;
begin
  //ScreenBuffer.Init(16, 16);
  //for I := 0 to 255 do
    // PUInt32(ScreenBuffer.Data)[I] := $ffffffff;
  //  PUInt32(ScreenBuffer.Data)[I] := $ff0000ff;
  FTexture.InitOwnership(4, 4, @DEFAULT_TEXTURE[0]);
  FFlags := FLAG_DEFAULT or FLAG_MEMORY;
end;

procedure TDrawTexParameters.InitFilename(const FileName: AnsiString; Flags: Cardinal = FLAG_MIPMAP);
begin
  FName := FileName;
  FFileName := FileName;
  FFlags := Flags;
end;

function TDrawTexParameters.MakeId: AnsiString;
begin
  Result := FName;
  if FFlags <> 0 then begin
    Result := Result + ' +';
    if FFlags and FLAG_MIPMAP <> 0 then begin
      Result := Result + 'm';
    end;
  end;
end;

function TDrawTexType.GetSingleton: PDrawTexType;
begin
  Result := @DrawTexType;
end;

function TDrawTexType.OnAllocHolder(ResourceRecord: PResourceRecord;
                       Parameters: Pointer;
                       out Holder: Pointer): Boolean;
begin
  New(PDrawTexHolder(Holder));
  PDrawTexHolder(Holder)^.FParameters := PDrawTexParameters(Parameters)^;
  PDrawTexHolder(Holder)^.Id := 0; // TODO default
  PDrawTexHolder(Holder)^.Width := 1;
  PDrawTexHolder(Holder)^.Height := 1;
  PDrawTexHolder(Holder)^.FRecord := ResourceRecord;
  Exit(True);
end;

function TDrawTexType.OnAllocDefaultHolder(ResourceRecord: PResourceRecord;
                       out Holder: Pointer): Boolean;
begin
  New(PDrawTexHolder(Holder));
  PDrawTexHolder(Holder)^.FParameters.InitDefault;
  PDrawTexHolder(Holder)^.Id := 0; // TODO default
  PDrawTexHolder(Holder)^.Width := 1;
  PDrawTexHolder(Holder)^.Height := 1;
  PDrawTexHolder(Holder)^.FRecord := ResourceRecord;
  Exit(True);
end;

function TDrawTexType.OnAllocHotSwapingHolder(ResourceRecord: PResourceRecord;
                                 Original: Pointer;
                                 out Holder: Pointer): Boolean;
begin
  Exit(False);
end;

function TDrawTexType.OnThreadedLoad(Holder: Pointer): Boolean;
begin
  with PDrawTexHolder(Holder)^ do begin
  with FParameters do begin
    if FFlags and FLAG_MEMORY = 0 then begin
      if not LoadTexture2d(FFileName, FTexture) then begin
        LogTagged('DT', 'FAILED LOADING ' + GetRecord^.GetHumanReadableId);
        Exit(GetRecord^.SetError(1, 'failed reading filed'));
      end;
    end;
    //Log('DT', 'Threaded load ' + GetRecord^.GetHumanReadableId);
    Exit(True);
  end;
  end;
end;

function TDrawTexType.OnLoad(Holder: Pointer): Boolean;
begin
  with PDrawTexHolder(Holder)^ do begin
  with FParameters do begin
    Width := FTexture.Width;
    Height := FTexture.Height;

    glGenTextures(1, @Id);
    glBindTexture(GL_TEXTURE_2D, Id);
    //glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // set 1-byte alignment

    // set texture to repeat mode
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

    if FFlags and TDrawTexParameters.FLAG_MIPMAP <> 0 then begin
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
      //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
      {$IFDEF ANDROID}
      //glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
      {$ENDIF}

      {$IFNDEF ANDROID}
      gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGBA,
                        FTexture.Width, FTexture.Height, GL_RGBA,
                        GL_UNSIGNED_BYTE, FTexture.Data);
      {$ELSE}
      glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
                   FTexture.Width, FTexture.Height, 0, GL_RGBA,
                   GL_UNSIGNED_BYTE, FTexture.Data);
      {$ENDIF}
    end else begin
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
      glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
                   FTexture.Width, FTexture.Height, 0, GL_RGBA,
                   GL_UNSIGNED_BYTE, FTexture.Data);
    end;
    //glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 2.0);
    glDisable(GL_LIGHTING);
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_DITHER);
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

    glBindTexture(GL_TEXTURE_2D, 0);

    if FFlags and FLAG_DEFAULT = 0 then
      LogTagged('DT', 'LOADED ' + GetRecord^.GetHumanReadableId);
    Exit(True);
  end;
  end;
end;

function TDrawTexType.OnUnload(Holder: Pointer): Boolean;
begin
  with PDrawTexHolder(Holder)^ do with FParameters do begin
    glDeleteTextures(1, @Id);
    if FFlags and FLAG_DEFAULT = 0 then
      LogTagged('DT', 'UNLOADED ' + GetRecord^.GetHumanReadableId);
    Exit(True);
  end;
end;

function TDrawTexType.OnThreadedUnload(Holder: Pointer): Boolean;
begin
  Exit(False);
end;

function TDrawTexType.OnDeallocHolder(Holder: Pointer): Boolean;
begin
  Exit(False);
end;

function TDrawTexType.OnHotSwap(Old, New: PResourceRecord): Boolean;
begin
  Exit(False);
end;

procedure TDrawTex.Init(const FileName: AnsiString);
var
  Parameters: TDrawTexParameters;
begin
  Parameters.InitFilename(FileName);
  LoadInternal(nil, @Parameters, Parameters.MakeId);
end;

function TDrawTex.Load(const FileName: AnsiString): Boolean;
var
  Parameters: TDrawTexParameters;
begin
  Parameters.InitFilename(FileName);
  Result := LoadInternal(nil, @Parameters, Parameters.MakeId);
end;

procedure TDrawTex.Bind;
begin
  glEnable(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, FHolder^.Id);
end;

procedure TDrawTex.Unbind;
begin
  glDisable(GL_TEXTURE_2D);
end;

procedure TDrawTex.Draw(const P, S: TVec2f; FlipX: Boolean = False; FlipY: Boolean = False);
{$IFDEF ANDROID}
var
  Vertices2f: array[0..3] of TVec2f;
  TexCoords2f: array[0..3] of TVec2f;
  Indices: array[0..5] of TIndexBuffer.TIndex;
  VB: TVertexBuffer;
  IB: TIndexBuffer;
begin
  Bind;
  TexCoords2f[0] := Vec2f(Ord(    FlipX), Ord(    FlipY));
  TexCoords2f[1] := Vec2f(Ord(not FlipX), Ord(    FlipY));
  TexCoords2f[2] := Vec2f(Ord(not FlipX), Ord(not FlipY));
  TexCoords2f[3] := Vec2f(Ord(    FlipX), Ord(not FlipY));
  Vertices2f[0] := Vec2f(P.X - S.X / 2, P.Y - S.Y / 2);
  Vertices2f[1] := Vec2f(P.X + S.X / 2, P.Y - S.Y / 2);
  Vertices2f[2] := Vec2f(P.X + S.X / 2, P.Y + S.Y / 2);
  Vertices2f[3] := Vec2f(P.X - S.X / 2, P.Y + S.Y / 2);
  VB.Init(Vertices2f);
  VB.SetTexCoords(TexCoords2f);
  Indices[0] := 0; Indices[1] := 1; Indices[2] := 3;
  Indices[3] := 1; Indices[4] := 2; Indices[5] := 3;
  IB.Init(Indices);
  VB.DrawTriangles(IB);
  IB.Done;
  VB.Done;
  Unbind;
end;
{$ELSE}
begin
  Bind;
  glBegin(GL_QUADS);
    glTexCoord2f(Ord(    FlipX), Ord(    FlipY));
    glVertex2f(P.X - S.X / 2, P.Y - S.Y / 2);
    glTexCoord2f(Ord(not FlipX), Ord(    FlipY));
    glVertex2f(P.X + S.X / 2, P.Y - S.Y / 2);
    glTexCoord2f(Ord(not FlipX), Ord(not FlipY));
    glVertex2f(P.X + S.X / 2, P.Y + S.Y / 2);
    glTexCoord2f(Ord(    FlipX), Ord(not FlipY));
    glVertex2f(P.X - S.X / 2, P.Y + S.Y / 2);
  glEnd;
  Unbind;
end;
{$ENDIF}


initialization
  DrawTexType.Init(TResourceType.FLAG_THREADED_LOAD);
finalization
  DrawTexType.Done;
end.
