unit frmMain;

interface

uses
  {$IFDEF MSWINDOWS} Windows, ShellAPI, {$ENDIF}
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Layouts,
  FMX.Controls.Presentation, FMX.StdCtrls, FMX.Effects, FMX.Objects, FMX.Filter.Effects,
  untCard, untAPI, FMX.ListBox, FMX.Edit, FMX.DialogService, System.Generics.Collections;

type
  TFormMain = class(TForm)
    tbMain: TToolBar;
    layMain: TLayout;
    layField: TLayout;
    lay1: TLayout;
    lay2: TLayout;
    lay3: TLayout;
    lay4: TLayout;
    lay5: TLayout;
    lay6: TLayout;
    lay7: TLayout;
    btnP0S0: TCornerButton;
    btnP0Deck: TCornerButton;
    btnP0S4: TCornerButton;
    btnP0S3: TCornerButton;
    btnP0S2: TCornerButton;
    btnP0S1: TCornerButton;
    btnNew: TCornerButton;
    btnP0Hand: TCornerButton;
    btnP1Hand: TCornerButton;
    btnP1Deck: TCornerButton;
    btnP1S4: TCornerButton;
    btnP1Extra: TCornerButton;
    btnP1S0: TCornerButton;
    btnP1S1: TCornerButton;
    btnP1S2: TCornerButton;
    btnP1S3: TCornerButton;
    btnP1M4: TCornerButton;
    btnP1S5: TCornerButton;
    btnP1M0: TCornerButton;
    btnP1M1: TCornerButton;
    btnP1M2: TCornerButton;
    btnP1M3: TCornerButton;
    btnM6: TCornerButton;
    btnM5: TCornerButton;
    btnP0S5: TCornerButton;
    btnP0M0: TCornerButton;
    btnP0M4: TCornerButton;
    btnP0M3: TCornerButton;
    btnP0M2: TCornerButton;
    btnP0M1: TCornerButton;
    btnP0Extra: TCornerButton;
    btnP1Grave: TCornerButton;
    btnP1Removed: TCornerButton;
    btnP0Removed: TCornerButton;
    btnP0Grave: TCornerButton;
    InnerGlowEffect9: TInnerGlowEffect;
    txtOwner: TText;
    txtFieldName: TLabel;
    layAddCard: TLayout;
    btnAddCard: TCornerButton;
    layEditList: TLayout;
    btnAddCardToList: TCornerButton;
    layDelCard: TLayout;
    btnDelCard: TCornerButton;
    gbProperty: TGroupBox;
    layCardInfo: TLayout;
    lblCardName: TLabel;
    lblCardType: TLabel;
    cmbCardOwner: TComboBox;
    cmbCardPosition: TComboBox;
    chkCardProc: TCheckBox;
    edtCardIndicator: TEdit;
    cmbCardEquipTo: TComboBox;
    btnCardContinousEffectTo: TCornerButton;
    btnXyzElements: TCornerButton;
    cmbCardWorkAs: TComboBox;
    btnCardOverride: TCornerButton;
    lstCardList: TListBox;
    lblLoginUser: TLabel;
    btnSave: TCornerButton;
    btnOpen: TCornerButton;
    btnConfig: TCornerButton;
    btnDebug: TCornerButton;
    btnUpload: TCornerButton;
    btnAbout: TCornerButton;
    procedure FormCreate(Sender: TObject);
    procedure fieldElementClick(Sender: TObject);
    procedure btnAddCardClick(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure btnNewClick(Sender: TObject);
    procedure btnAddCardToListClick(Sender: TObject);
    procedure btnDelCardClick(Sender: TObject);
    procedure chkCardProcChange(Sender: TObject);
    procedure chkCardProcClick(Sender: TObject);
    procedure cmbCardEquipToChange(Sender: TObject);
    procedure cmbCardOwnerChange(Sender: TObject);
    procedure cmbCardPositionChange(Sender: TObject);
    procedure cmbCardWorkAsChange(Sender: TObject);
    procedure edtCardIndicatorChange(Sender: TObject);
    procedure edtCardIndicatorTyping(Sender: TObject);
    procedure btnOpenClick(Sender: TObject);
    procedure btnSaveClick(Sender: TObject);
    procedure btnConfigClick(Sender: TObject);
    procedure btnDebugClick(Sender: TObject);
    procedure btnUploadClick(Sender: TObject);
    procedure btnCardContinousEffectToClick(Sender: TObject);
    procedure btnXyzElementsClick(Sender: TObject);
    procedure btnAboutClick(Sender: TObject);
    procedure btnCardOverrideClick(Sender: TObject);
  private
    BHand: array[0..1] of TCornerButton;
    BDeck: array[0..1] of TCornerButton;
    BExtraDeck: array[0..1] of TCornerButton;
    BGrave: array[0..1] of TCornerButton;
    BRemoved: array[0..1] of TCornerButton;
    BMonster: array[0..1, 0..4] of TCornerButton;
    BSpell: array[0..1, 0..5] of TCornerButton;
    BExtraMonster: array[5..6] of TCornerButton;

    ImgMonster: array[0..1, 0..4] of TImage;
    ImgSpell: array[0..1, 0..5] of TImage;
    ImgExtraMonster: array[5..6] of TImage;

    FFilePath: string;
    FPuzzle: TPuzzleField;
    
    FFieldInfo: TFieldRec;
    FCurrentCard: ICard;
    FCurrentCardList: TList<ICard>;

    FUserToken: String;
    FUserInfo: TMCUser;

    procedure currentCardListAddOne(ACard: ICard);
    procedure currentCardListDelete(ACardId: Int64; AIndex: Integer);
    procedure NewPuzzle();
    procedure OpenPuzzle(AFilePath: string);
    procedure SavePuzzle();
    procedure RefreshFieldCards();
    procedure PerformFieldClick();

    procedure LoadCardImage();
    procedure UnloadCardImage();

    // equip to
    procedure ReEquipTo();
    // card list
    procedure RefreshCardList();

    // loca user
    procedure LoadUser();
    function userExec(ATID: TThreadID; AData: String): TMCUser;
    procedure userTerm(ATID: TThreadID; AData: TMCUser);
    function loginExec(ATID: TThreadID; AData: TMCSignInReq): TMCUserWithToken;
    procedure loginTerm(ATID: TThreadID; AData: TMCUserWithToken);

    // upload puzzle
    procedure UploadPuzzle();
  public
    { Public declarations }
  end;

var
  FormMain: TFormMain;

implementation

uses
  untUtils, frmSearchCard, untTextItem, untArrayHelper, untCardJson, untCardOperationItem, moduleData,
  untConfig, untThread, untCardEquipItem, untScriptGenerator, frmAbout, frmCardList, frmLogin, frmContinousEffected,
  frmPuzzleConfig, frmOverride, frmStyleModule;

{$R *.fmx}

procedure TFormMain.btnAboutClick(Sender: TObject);
begin
  // About
  with TFormAbout.Create(Self) do begin
    ShowModal;
    Free;
  end;
end;

procedure TFormMain.btnAddCardClick(Sender: TObject);
var
  cid: Int64;
  c: ICard;
begin
  cid := -1;
  with TFormSearchCard.Create(Self) do begin
    if ShowModal = mrok then begin
       cid := CardId;
    end;
    Free;
  end;
  if cid = -1 then Exit;

  c := DMData.GetCardInfo(cid);
  if c <> nil then begin
    FCurrentCard := c;
    case FFieldInfo.Owner of
    foSelf:
      begin
        c.SetOwner(coSelf);
        c.SetPlayer(cpSelf);
        case FFieldInfo.Location of
        clMZone:
          begin
            FPuzzle.MCard[0, FFieldInfo.Seq] := c;
            c.SetLocation(clMZone);
            c.SetPosition(cpUpAtk);
          end;
        clSZone:
          begin
            FPuzzle.SCard[0, FFieldInfo.Seq] := c;
            c.SetLocation(clSZone);
            c.SetPosition(cpUp);
          end;
        end;
        c.SetSeq(FFieldInfo.Seq);
      end;
    foOpponent:
      begin
        c.SetOwner(coOpponent);
        c.SetPlayer(cpOpponent);
        case FFieldInfo.Location of
        clMZone:
          begin
            FPuzzle.MCard[1, FFieldInfo.Seq] := c;
            c.SetLocation(clMZone);
            c.SetPosition(cpUpAtk);
          end;
        clSZone:
          begin
            FPuzzle.SCard[1, FFieldInfo.Seq] := c;
            c.SetLocation(clSZone);
            c.SetPosition(cpUp);
          end;
        end;
        c.SetSeq(FFieldInfo.Seq);
      end;
    foShare:
      begin
        c.SetOwner(coSelf);
        c.SetPlayer(cpSelf);
        FPuzzle.ExtraMonster[FFieldInfo.Seq] := c;
        c.SetLocation(clMZone);
        c.SetPosition(cpUpAtk);
        c.SetSeq(FFieldInfo.Seq);
      end;
    end;
    lblCardName.Text:= Format('%s (%d)', [FCurrentCard.GetCardName, FCurrentCard.GetCardId]);
    layAddCard.Visible:= False;
    layDelCard.Visible:= True;
    layCardInfo.Visible:= True;
    // load card image
    LoadCardImage();
    PerformFieldClick();
  end;
end;

procedure TFormMain.btnAddCardToListClick(Sender: TObject);
var
  cid: Int64;
  item: TCardOperItem;
  c: ICard;
begin
  cid := 0;
  // if (FCurrentCardList = nil) then Exit;
  // add card to list
  with TFormSearchCard.Create(Self) do begin
    if ShowModal = mrOK then begin
      cid := CardId;
    end;
    Free;
  end;
  if cid > 0 then begin
    c := DMData.GetCardInfo(cid);
    FCurrentCardList.Add(c);
    item := TCardOperItem.Create(lstCardList);
    item.CardName:= c.GetCardName;
    item.CardId:= cid;
    item.Card := c;
    item.Index:= lstCardList.Count - 1;
    item.OnDelete:= currentCardListDelete;
    item.OnCardAddOneClicked:= currentCardListAddOne;
    if (FFieldInfo.Location = clGrave) then begin
      item.ShowFace := False;
      item.Face := 0; // up
    end else begin
      item.ShowFace := True;
      case FFieldInfo.Location of
      clHand, clDeck, clExtra: item.Face := 1; // down
      clRemoved: item.Face := 0; // up
      end;
    end;;
  end;
end;

procedure TFormMain.btnCardContinousEffectToClick(Sender: TObject);
begin
  // btnCardContinousEffectTo click
  with TFormContinousEffected.Create(Self) do begin
    CEPuzzle := FPuzzle;
    CECurrentCard := FCurrentCard;
    RefreshList();
    if ShowModal = mrOK then begin
      FCurrentCard.SetContinousEffectTo(CESelectedCards);
      btnCardContinousEffectTo.Text:= Format('共 %d 张卡', [FCurrentCard.GetContinousEffectTo.Count]);
    end;
    Free;
  end;
end;

procedure TFormMain.btnCardOverrideClick(Sender: TObject);
begin
  // btnOverride click
  with TFormOverride.Create(Self) do begin
    OverrideCard := FCurrentCard;
    if ShowModal = mrOk then begin
      FCurrentCard.SetOverride(OverrideInfo);
    end;
    Free;
  end;
end;

procedure TFormMain.btnConfigClick(Sender: TObject);
begin
  // btnConfig click
  with TFormPuzzleConfig.Create(Self) do begin
    PPuzzleName:= FPuzzle.PuzzleName;
    PAIName:= FPuzzle.AIName;
    P0LP:= FPuzzle.LPSelf;
    P1LP:= FPuzzle.LPOpponent;
    PMessage:= FPuzzle.Message;
    PSolution:= FPuzzle.Solution;
    if ShowModal = mrOK then begin
      FPuzzle.PuzzleName:= PPuzzleName;
      FPuzzle.AIName:= PAIName;
      FPuzzle.LPSelf:= P0LP;
      FPuzzle.LPOpponent:= P1LP;
      FPuzzle.Message:= PMessage;
      FPuzzle.Solution:= PSolution;
    end;
    Free;
  end;
end;

procedure TFormMain.btnDebugClick(Sender: TObject);
const
  DEBUG_NAME = 'debug';
var
  ADir: string;
  ALuaFileName: string;
  ACode: string;
  AMDPro3Path: string;
begin
  SavePuzzle();
  if (FFilePath = '') then Exit;
  ADir:= TConfig.GetMDPro3InstallPath();
  ALuaFileName:= ADir + PathDelim + 'Puzzle' + PathDelim + DEBUG_NAME + '.lua';
  ACode:= TScriptGenerator.Generate(FPuzzle);
  with TStringList.Create do begin
    Text:= ACode;
    WriteBOM := False;
    SaveToFile(ALuaFileName, TEncoding.UTF8);
    Free;
  end;

  // debug
  {$IFDEF  MSWINDOWS}
  AMDPro3Path:= ADir + PathDelim + 'MDPro3.exe';
  ShellExecute(0, 'open', PChar(AMDPro3Path), PChar(Format('-s %s', [DEBUG_NAME])), PChar(ADir), SW_SHOWNORMAL);
  {$ENDIF}
end;

procedure TFormMain.btnDelCardClick(Sender: TObject);
begin
  // del card
  if (FCurrentCard = nil) then Exit;
  case FFieldInfo.Owner of
  foSelf:
    begin
      case FFieldInfo.Location of
      clMZone: FPuzzle.MCard[0, FFieldInfo.Seq] := nil;
      clSZone: FPuzzle.SCard[0, FFieldInfo.Seq] := nil;
      end;
    end;
  foOpponent:
    begin
      case FFieldInfo.Location of
      clMZone: FPuzzle.MCard[1, FFieldInfo.Seq] := nil;
      clSZone: FPuzzle.SCard[1, FFieldInfo.Seq] := nil;
      end;
    end;
  foShare:
    begin
      FPuzzle.ExtraMonster[FFieldInfo.Seq] := nil;
    end;
  end;
  layAddCard.Visible:= True;
  layDelCard.Visible:= False;
  layCardInfo.Visible:= False;
  // unload image
  UnloadCardImage();
  FPuzzle.Optimize();
  PerformFieldClick();
end;

procedure TFormMain.btnNewClick(Sender: TObject);
begin
  NewPuzzle();
end;

procedure TFormMain.btnOpenClick(Sender: TObject);
begin
  with TOpenDialog.Create(Self) do begin
    Filter:= 'JSON|*.json';
    if (Execute) then begin
      OpenPuzzle(FileName);
    end;
    Free;
  end;
end;

procedure TFormMain.btnSaveClick(Sender: TObject);
begin
  if (FPuzzle = nil) then Exit;
  SavePuzzle();
end;

procedure TFormMain.btnUploadClick(Sender: TObject);
var
  req: TMCSignInReq;
begin
  if FUserToken = '' then begin
    // login
    with TFormLogin.Create(Self) do begin
      if ShowModal = mrOk then begin
        req := TMCSignInReq.Create(UserAccount, UserPassword);
        ThreadEXT.ThreadExecute<TMCSignInReq, TMCUserWithToken>(req, loginExec, loginTerm);
      end;
      Free;
    end;
    Exit;
  end;

  if (FUserToken <> '') then begin
    UploadPuzzle();
  end;
end;

procedure TFormMain.btnXyzElementsClick(Sender: TObject);
begin
  // btnXyzElements click
  if (FCurrentCard = nil) then Exit;
  // xys elements
  with TFormCardList.Create(Self) do begin
    FillCardIds(FCurrentCard.GetXyzElements);
    if ShowModal = mrOK then begin
      FCurrentCard.SetXyzElements(CardList);
      btnXyzElements.Text:= Format('共 %d 张卡', [FCurrentCard.GetXyzElements.Count]);
    end;
    Free;
  end;
end;

procedure TFormMain.chkCardProcChange(Sender: TObject);
begin
  if (FCurrentCard = nil) then Exit;
  FCurrentCard.SetProc(chkCardProc.IsChecked);
end;

procedure TFormMain.chkCardProcClick(Sender: TObject);
begin
  if (FCurrentCard = nil) then Exit;
  FCurrentCard.SetProc(chkCardProc.IsChecked);
end;

procedure TFormMain.cmbCardEquipToChange(Sender: TObject);
var
  idx: Integer;
  item: TCardEquipToItem;
begin
  if (FCurrentCard = nil) then Exit;

  idx := cmbCardEquipTo.ItemIndex;
  if idx = 0 then begin
    FCurrentCard.SetEquipTo(nil);
  end else begin
    item := TCardEquipToItem(cmbCardEquipTo.ListBox.ListItems[idx]);
    FCurrentCard.SetEquipTo(item.Card);
  end;
end;

procedure TFormMain.cmbCardOwnerChange(Sender: TObject);
begin
  if (FCurrentCard = nil) then Exit;
  FCurrentCard.SetOwner(EXT.IfThen<TCardOwner>(cmbCardOwner.ItemIndex = 0, coSelf, coOpponent));
  // the card in extra monster zone
  if (FFieldInfo.Owner = foShare) then begin
    FCurrentCard.SetPlayer(EXT.IfThen<TCardPlayer>(cmbCardOwner.ItemIndex = 0, cpSelf, cpOpponent));
    if (FCurrentCard.GetOwner = coSelf) then begin
      case FCurrentCard.GetSeq of
      5:ImgExtraMonster[5].RotationAngle := 0;
      6: ImgExtraMonster[6].RotationAngle := 0;
      end;
    end else begin
      case FCurrentCard.GetSeq of
      5: ImgExtraMonster[5].RotationAngle := 180;
      6: ImgExtraMonster[6].RotationAngle := 180;
      end;
    end;
  end;
end;

procedure TFormMain.cmbCardPositionChange(Sender: TObject);
var
  AImgPath: string;
begin
  if FCurrentCard = nil then Exit;
  
  case FCurrentCard.GetOwner of
  coSelf:
    begin
      case FCurrentCard.GetLocation of
      clMZone:
        begin
          // skip link monsters
          if FCurrentCard.GetSeq >= 5 then begin
            if FCurrentCard.GetIsLinkMonster then Exit;
            case cmbCardPosition.ItemIndex of
            0:  // up atk
              begin
                AImgPath:= ExtractFilePath(ParamStr(0)) + PathDelim + 'cardimg' + PathDelim + Format('%d.jpg', [FCurrentCard.GetCardId]);
                ImgExtraMonster[FCurrentCard.GetSeq].Bitmap.LoadFromFile(AImgPath);
                ImgExtraMonster[FCurrentCard.GetSeq].RotationAngle:= 0;
                FCurrentCard.SetPosition(cpUpAtk);
              end;
            1:  // up def
              begin
                AImgPath:= ExtractFilePath(ParamStr(0)) + PathDelim + 'cardimg' + PathDelim + Format('%d.jpg', [FCurrentCard.GetCardId]);
                ImgExtraMonster[FCurrentCard.GetSeq].Bitmap.LoadFromFile(AImgPath);
                ImgExtraMonster[FCurrentCard.GetSeq].RotationAngle:= 90;
                FCurrentCard.SetPosition(cpUpDef);
              end;
            2:  // down def
              begin
                AImgPath:= ExtractFilePath(ParamStr(0)) + PathDelim + 'cardimg' + PathDelim + '0.jpg';
                ImgExtraMonster[FCurrentCard.GetSeq].Bitmap.LoadFromFile(AImgPath);
                ImgExtraMonster[FCurrentCard.GetSeq].RotationAngle:= 90;
                FCurrentCard.SetPosition(cpDownDef);
              end;
            end;
            Exit;
          end;
          case cmbCardPosition.ItemIndex of
          0:  // up atk
            begin
              AImgPath:= ExtractFilePath(ParamStr(0)) + PathDelim + 'cardimg' + PathDelim + Format('%d.jpg', [FCurrentCard.GetCardId]);
              ImgMonster[0][FCurrentCard.GetSeq].Bitmap.LoadFromFile(AImgPath);
              ImgMonster[0][FCurrentCard.GetSeq].RotationAngle:= 0;
              FCurrentCard.SetPosition(cpUpAtk);
            end;
          1:  // up def
            begin
              AImgPath:= ExtractFilePath(ParamStr(0)) + PathDelim + 'cardimg' + PathDelim + Format('%d.jpg', [FCurrentCard.GetCardId]);
              ImgMonster[0][FCurrentCard.GetSeq].Bitmap.LoadFromFile(AImgPath);
              ImgMonster[0][FCurrentCard.GetSeq].RotationAngle:= 90;
              FCurrentCard.SetPosition(cpUpDef);
            end;
          2:  // down def
            begin
              AImgPath:= ExtractFilePath(ParamStr(0)) + PathDelim + 'cardimg' + PathDelim + '0.jpg';
              ImgMonster[0][FCurrentCard.GetSeq].Bitmap.LoadFromFile(AImgPath);
              ImgMonster[0][FCurrentCard.GetSeq].RotationAngle:= 90;
              FCurrentCard.SetPosition(cpDownDef);
            end;
          end;
        end;
      clSZone:
        begin
          // skip pendulum monsters
          if FCurrentCard.GetCardType = ctPendulum then Exit;
          case cmbCardPosition.ItemIndex of
          0:  // up
            begin
              AImgPath:= ExtractFilePath(ParamStr(0)) + PathDelim + 'cardimg' + PathDelim + Format('%d.jpg', [FCurrentCard.GetCardId]);
              ImgSpell[0][FCurrentCard.GetSeq].Bitmap.LoadFromFile(AImgPath);
              FCurrentCard.SetPosition(cpUp);
            end;
          1:  // down
            begin
              AImgPath:= ExtractFilePath(ParamStr(0)) + PathDelim + 'cardimg' + PathDelim + '0.jpg';
              ImgSpell[0][FCurrentCard.GetSeq].Bitmap.LoadFromFile(AImgPath);
              FCurrentCard.SetPosition(cpDown);
            end;
          end;
        end;
      end;
    end;
  coOpponent:
    begin
      case FCurrentCard.GetLocation of
      clMZone:
        begin
          // skip link monsters
          if FCurrentCard.GetSeq >= 5 then begin
            if FCurrentCard.GetIsLinkMonster then Exit;
            case cmbCardPosition.ItemIndex of
            0:  // up atk
              begin
                AImgPath:= ExtractFilePath(ParamStr(0)) + PathDelim + 'cardimg' + PathDelim + Format('%d.jpg', [FCurrentCard.GetCardId]);
                ImgExtraMonster[FCurrentCard.GetSeq].Bitmap.LoadFromFile(AImgPath);
                ImgExtraMonster[FCurrentCard.GetSeq].RotationAngle:= 180;
                FCurrentCard.SetPosition(cpUpAtk);
              end;
            1:  // up def
              begin
                AImgPath:= ExtractFilePath(ParamStr(0)) + PathDelim + 'cardimg' + PathDelim + Format('%d.jpg', [FCurrentCard.GetCardId]);
                ImgExtraMonster[FCurrentCard.GetSeq].Bitmap.LoadFromFile(AImgPath);
                ImgExtraMonster[FCurrentCard.GetSeq].RotationAngle:= 270;
                FCurrentCard.SetPosition(cpUpDef);
              end;
            2:  // down def
              begin
                AImgPath:= ExtractFilePath(ParamStr(0)) + PathDelim + 'cardimg' + PathDelim + '0.jpg';
                ImgExtraMonster[FCurrentCard.GetSeq].Bitmap.LoadFromFile(AImgPath);
                ImgExtraMonster[FCurrentCard.GetSeq].RotationAngle:= 270;
                FCurrentCard.SetPosition(cpDownDef);
              end;
            end;
            Exit;
          end;
          case cmbCardPosition.ItemIndex of
          0:  // up atk
            begin
              AImgPath:= ExtractFilePath(ParamStr(0)) + PathDelim + 'cardimg' + PathDelim + Format('%d.jpg', [FCurrentCard.GetCardId]);
              ImgMonster[1][FCurrentCard.GetSeq].Bitmap.LoadFromFile(AImgPath);
              ImgMonster[1][FCurrentCard.GetSeq].RotationAngle:= 180;
              FCurrentCard.SetPosition(cpUpAtk);
            end;
          1:  // up def
            begin
              AImgPath:= ExtractFilePath(ParamStr(0)) + PathDelim + 'cardimg' + PathDelim + Format('%d.jpg', [FCurrentCard.GetCardId]);
              ImgMonster[1][FCurrentCard.GetSeq].Bitmap.LoadFromFile(AImgPath);
              ImgMonster[1][FCurrentCard.GetSeq].RotationAngle:= 270;
              FCurrentCard.SetPosition(cpUpDef);
            end;
          2:  // down def
            begin
              AImgPath:= ExtractFilePath(ParamStr(0)) + PathDelim + 'cardimg' + PathDelim + '0.jpg';
              ImgMonster[1][FCurrentCard.GetSeq].Bitmap.LoadFromFile(AImgPath);
              ImgMonster[1][FCurrentCard.GetSeq].RotationAngle:= 270;
              FCurrentCard.SetPosition(cpDownDef);
            end;
          end;
        end;
      clSZone:
        begin
          // skip pendulum monsters
          if FCurrentCard.GetCardType = ctPendulum then Exit;
          case cmbCardPosition.ItemIndex of
          0:  // up
            begin
              AImgPath:= ExtractFilePath(ParamStr(0)) + PathDelim + 'cardimg' + PathDelim + Format('%d.jpg', [FCurrentCard.GetCardId]);
              ImgSpell[1][FCurrentCard.GetSeq].Bitmap.LoadFromFile(AImgPath);
              FCurrentCard.SetPosition(cpUp);
            end;
          1:  // down
            begin
              AImgPath:= ExtractFilePath(ParamStr(0)) + PathDelim + 'cardimg' + PathDelim + '0.jpg';
              ImgSpell[1][FCurrentCard.GetSeq].Bitmap.LoadFromFile(AImgPath);
              FCurrentCard.SetPosition(cpDown);
            end;
          end;
        end;
      end;
    end;
  end;
end;

procedure TFormMain.cmbCardWorkAsChange(Sender: TObject);
begin
  if (FCurrentCard = nil) then Exit;
  FCurrentCard.SetWorkAs(TCardWorkAs(cmbCardWorkAs.ItemIndex));
end;

procedure TFormMain.currentCardListAddOne(ACard: ICard);
var
  i: Integer;
  item: TCardOperItem;
  c: ICard;
begin
  if (FCurrentCardList = nil) then Exit;
  c := DMData.GetCardInfo(ACard.GetCardId);
  FCurrentCardList.Add(c);
  item := TCardOperItem.Create(lstCardList);
  item.CardName:= c.GetCardName;
  item.CardId:= c.GetCardId;
  item.Card := c;
  item.Index := lstCardList.Count - 1;
  item.OnDelete:= currentCardListDelete;
  item.OnCardAddOneClicked:= currentCardListAddOne;

  if (FFieldInfo.Location = clGrave) then begin
    item.ShowFace := False;
    item.Face := 0; // up
  end else begin
    item.ShowFace := True;
    case FFieldInfo.Location of
    clHand, clDeck, clExtra: item.Face := 1; // down
    clRemoved: item.Face := 0; // up
    end;
  end;

  // rebuild index
  for i := 0 to lstCardList.Count - 1 do begin
    item := TCardOperItem(lstCardList.ListItems[i]);
    item.Index:= i;
  end;
end;

procedure TFormMain.currentCardListDelete(ACardId: Int64; AIndex: Integer);
var
  i, j: Integer;
  c: ICard;
  item: TCardOperItem;
begin
  if (FCurrentCardList = nil) then Exit;

  for i := 0 to FCurrentCardList.Count - 1 do begin
    c := FCurrentCardList[i];
    if c.GetCardId = ACardId then begin
      for j := 0 to lstCardList.Count - 1 do begin
        item := TCardOperItem(lstCardList.ListItems[j]);
        if (item.CardId = ACardId) and (item.Index = AIndex) then begin
          FCurrentCardList.Delete(i);
          item.Free;
          Break;
        end;
      end;
      Break;
    end;
  end;

  // rebuild index
  for i := 0 to lstCardList.Count - 1 do begin
    item := TCardOperItem(lstCardList.ListItems[i]);
    item.Index:= i;
  end;

end;

procedure TFormMain.edtCardIndicatorChange(Sender: TObject);
begin
  if (FCurrentCard = nil) then Exit;
  FCurrentCard.SetIndicatorCount(StrToIntDef(edtCardIndicator.Text, 0));
end;

procedure TFormMain.edtCardIndicatorTyping(Sender: TObject);
begin
  if (FCurrentCard = nil) then Exit;
  FCurrentCard.SetIndicatorCount(StrToIntDef(edtCardIndicator.Text, 0));
end;

procedure TFormMain.fieldElementClick(Sender: TObject);
var
  n: string;
  fieldInfo: TFieldRec;
  fieldStr: TFieldStrings;
  card: ICard;
begin
  n := TCornerButton(Sender).Name;
  fieldInfo := ElementNameToFieldRec(n);
  fieldStr := ElementNameToFieldStrings(fieldInfo);
  txtOwner.Text:= fieldStr.Owner;
  txtOwner.TextSettings.FontColor := fieldStr.Color;
  txtFieldName.Text:= fieldStr.Location;

  // save a global record, for all operations besides this method.
  FFieldInfo :=  fieldInfo;

  layEditList.Visible:= False;
  layAddCard.Visible := False;
  layDelCard.Visible := False;
  layCardInfo.Visible := False;

  case fieldInfo.Location of
  clDeck, clGrave, clRemoved, clExtra, clHand:
    begin
      layEditList.Visible:= True;
      RefreshCardList();
    end;
  clMZone, clSZone:
    begin
      card := FPuzzle.GetCardByFieldRec(fieldInfo);
      if card = nil then begin
        layAddCard.Visible := True;
      end else begin
        layCardInfo.Visible := True;
        layDelCard.Visible:= True;

        FCurrentCard := card;
        lblCardName.Text:= Format('%s (%d)', [FCurrentCard.GetCardName, FCurrentCard.GetCardId]);
        lblCardType.Text:= CardTypeToReadable(FCurrentCard.GetCardType);
        cmbCardOwner.ItemIndex:= EXT.IfThen<Integer>(FCurrentCard.GetOwner = coSelf, 0, 1);
        // card position
        cmbCardPosition.Clear;

        case card.GetLocation of
        clMZone:
          begin
            if (card.GetIsLinkMonster) then begin
              cmbCardPosition.AddObject(TCardPositionItem.Create(cmbCardPosition, 0));
              cmbCardPosition.ItemIndex:= 0;
            end else begin
              cmbCardPosition.AddObject(TCardPositionItem.Create(cmbCardPosition, 0));
              cmbCardPosition.AddObject(TCardPositionItem.Create(cmbCardPosition, 1));
              cmbCardPosition.AddObject(TCardPositionItem.Create(cmbCardPosition, 2));
              case card.GetPosition of
              cpUpAtk: cmbCardPosition.ItemIndex:= 0;
              cpUpDef: cmbCardPosition.ItemIndex:= 1;
              cpDownDef: cmbCardPosition.ItemIndex:= 2;
              end;
            end;
            cmbCardEquipTo.Enabled := False;
            btnCardOverride.Enabled := True;
          end;
        clSZone:
          begin
            if (card.GetCardType = ctPendulum) then begin
              // can not set to face-down
              cmbCardPosition.AddObject(TCardPositionItem.Create(cmbCardPosition, 3));
              cmbCardPosition.ItemIndex:= 0;
            end else begin
              cmbCardPosition.AddObject(TCardPositionItem.Create(cmbCardPosition, 3));
              cmbCardPosition.AddObject(TCardPositionItem.Create(cmbCardPosition, 4));
              case card.GetPosition of
              cpUp: cmbCardPosition.ItemIndex:= 0;
              cpDown: cmbCardPosition.ItemIndex:=1;
              end;
            end;
            cmbCardEquipTo.Enabled := True;
            btnCardOverride.Enabled := False;
          end;
        end;

        // load details
        chkCardProc.IsChecked:= card.GetProc;
        edtCardIndicator.Text := IntToStr(card.GetIndicatorCount);
        
        ReEquipTo();
        btnCardContinousEffectTo.Text:= Format('共 %d 张卡', [card.GetContinousEffectTo.Count]);
        btnXyzElements.Enabled:= card.GetIsXyzMonster;
        if (card.GetIsXyzMonster) then begin
          btnXyzElements.Text:= Format('共 %d 张卡', [card.GetXyzElements.Count]);
        end else begin
          btnXyzElements.Text := '非超量怪兽';
        end;

        cmbCardWorkAs.ItemIndex := Ord(card.GetWorkAs);
      end;
    end;
  end;
end;

procedure TFormMain.FormCreate(Sender: TObject);
var
  i, j: Integer;
  eff: TInnerGlowEffect;
  eff2: TInnerGlowEffect;
begin
  FPuzzle := nil;
  FFilePath:= '';
  FCurrentCard := nil;

  {$IFNDEF MSWINDOWS}
  btnDebug.Enabled := False;
  {$ENDIF}

  // init internal operation objects
  BHand[0] := btnP0Hand;
  BHand[1] := btnP1Hand;
  BDeck[0] := btnP0Deck;
  BDeck[1] := btnP1Deck;
  BExtraDeck[0] := btnP0Extra;
  BExtraDeck[1] := btnP1Extra;
  BGrave[0] := btnP0Grave;
  BGrave[1] := btnP1Grave;
  BRemoved[0] := btnP0Removed;
  BRemoved[1] := btnP1Removed;
  BMonster[0][0] := btnP0M0;
  BMonster[0][1] := btnP0M1;
  BMonster[0][2] := btnP0M2;
  BMonster[0][3] := btnP0M3;
  BMonster[0][4] := btnP0M4;
  BMonster[1][0] := btnP1M0;
  BMonster[1][1] := btnP1M1;
  BMonster[1][2] := btnP1M2;
  BMonster[1][3] := btnP1M3;
  BMonster[1][4] := btnP1M4;
  BSpell[0][0] := btnP0S0;
  BSpell[0][1] := btnP0S1;
  BSpell[0][2] := btnP0S2;
  BSpell[0][3] := btnP0S3;
  BSpell[0][4] := btnP0S4;
  BSpell[0][5] := btnP0S5;
  BSpell[1][0] := btnP1S0;
  BSpell[1][1] := btnP1S1;
  BSpell[1][2] := btnP1S2;
  BSpell[1][3] := btnP1S3;
  BSpell[1][4] := btnP1S4;
  BSpell[1][5] := btnP1S5;
  BExtraMonster[5] := btnM5;
  BExtraMonster[6] := btnM6;

  for i := 0 to 1 do begin
    for j := 0 to 4 do begin
      ImgMonster[i][j] := TImage.Create(BMonster[i][j]);
      ImgMonster[i][j].Parent := BMonster[i][j];
      ImgMonster[i][j].Align := TAlignLayout.Client;
      ImgMonster[i][j].HitTest := False;
      ImgMonster[i][j].Margins.Top := 2;
      ImgMonster[i][j].Margins.Bottom := 2;
      ImgMonster[i][j].Margins.Left := 2;
      ImgMonster[i][j].Margins.Right := 2;

      eff := TInnerGlowEffect.Create(Self);
      eff.Parent := BMonster[i][j];
      eff.Enabled := False;
      eff.GlowColor := $FF66F8E3;
      eff.Trigger := 'IsFocused=true';
      eff.Softness := 2;

      eff2 := TInnerGlowEffect.Create(Self);
      eff2.Parent := BMonster[i][j];
      eff2.Enabled := False;
      eff2.GlowColor := $3366F8E3;
      eff2.Trigger := 'IsMouseOver=true';
      eff2.Softness := 2;

      BMonster[i][j].Text:= Format('怪兽'#13#10'(%d)', [j]);
      if (j = 0) or (j = 4) then begin
        BSpell[i][j].Text:= Format('魔陷/灵摆'#13#10'(%d)', [j]);
      end else begin
        BSpell[i][j].Text:= Format('魔陷'#13#10'(%d)', [j]);
      end;
    end;
  end;
  for i := 0 to 1 do begin
    for j := 0 to 5 do begin
      ImgSpell[i][j] := TImage.Create(BSpell[i][j]);
      ImgSpell[i][j].Parent := BSpell[i][j];
      ImgSpell[i][j].Align := TAlignLayout.Client;
      ImgSpell[i][j].HitTest := False;
      ImgSpell[i][j].Margins.Top := 2;
      ImgSpell[i][j].Margins.Bottom := 2;
      ImgSpell[i][j].Margins.Left := 2;
      ImgSpell[i][j].Margins.Right := 2;

      eff := TInnerGlowEffect.Create(Self);
      eff.Parent := BSpell[i][j];
      eff.Enabled := False;
      eff.GlowColor := $FF66F8E3;
      eff.Trigger := 'IsFocused=true';
      eff.Softness := 2;

      eff2 := TInnerGlowEffect.Create(Self);
      eff2.Parent := BSpell[i][j];
      eff2.Enabled := False;
      eff2.GlowColor := $3366F8E3;
      eff2.Trigger := 'IsMouseOver=true';
      eff2.Softness := 2;
    end;
  end;

  for i := 5 to 6 do begin
    ImgExtraMonster[i] := TImage.Create(BExtraMonster[i]);
    ImgExtraMonster[i].Parent := BExtraMonster[i];
    ImgExtraMonster[i].Align := TAlignLayout.Client;
    ImgExtraMonster[i].HitTest := False;
    ImgExtraMonster[i].Margins.Top := 2;
    ImgExtraMonster[i].Margins.Bottom := 2;
    ImgExtraMonster[i].Margins.Left := 2;
    ImgExtraMonster[i].Margins.Right := 2;

    eff := TInnerGlowEffect.Create(Self);
    eff.Parent := BExtraMonster[i];
    eff.Enabled := False;
    eff.GlowColor := $FF66F8E3;
    eff.Trigger := 'IsFocused=true';
    eff.Softness := 2;

    eff2 := TInnerGlowEffect.Create(Self);
    eff2.Parent := BExtraMonster[i];
    eff2.Enabled := False;
    eff2.GlowColor := $3366F8E3;
    eff2.Trigger := 'IsMouseOver=true';
    eff2.Softness := 2;
  end;

  // give text
  BSpell[0][5].Text:= '场地'#13#10'(5)';
  BSpell[1][5].Text:= '场地'#13#10'(5)';
  BExtraMonster[5].Text:= '额外怪兽'#13#10'(5)';
  BExtraMonster[6].Text:= '额外怪兽'#13#10'(6)';

  // prop
  cmbCardOwner.AddObject(TCardOwnerPlayerItem.Create(cmbCardOwner, 0));
  cmbCardOwner.AddObject(TCardOwnerPlayerItem.Create(cmbCardOwner, 1));
  cmbCardOwner.ItemIndex:= 0;

  // work as
  cmbCardWorkAs.AddObject(TTextItem.Create(cmbCardWorkAs, '不改变'));
  cmbCardWorkAs.AddObject(TTextItem.Create(cmbCardWorkAs, '通常怪兽'));
  cmbCardWorkAs.AddObject(TTextItem.Create(cmbCardWorkAs, '效果怪兽'));
  cmbCardWorkAs.AddObject(TTextItem.Create(cmbCardWorkAs, '仪式怪兽'));
  cmbCardWorkAs.AddObject(TTextItem.Create(cmbCardWorkAs, '融合怪兽'));
  cmbCardWorkAs.AddObject(TTextItem.Create(cmbCardWorkAs, '同调怪兽'));
  cmbCardWorkAs.AddObject(TTextItem.Create(cmbCardWorkAs, '超量怪兽'));
  cmbCardWorkAs.AddObject(TTextItem.Create(cmbCardWorkAs, '连接怪兽'));
  cmbCardWorkAs.AddObject(TTextItem.Create(cmbCardWorkAs, '灵摆怪兽'));
  cmbCardWorkAs.AddObject(TTextItem.Create(cmbCardWorkAs, 'Token'));
  cmbCardWorkAs.AddObject(TTextItem.Create(cmbCardWorkAs, '通常魔法'));
  cmbCardWorkAs.AddObject(TTextItem.Create(cmbCardWorkAs, '装备魔法'));
  cmbCardWorkAs.AddObject(TTextItem.Create(cmbCardWorkAs, '场地魔法'));
  cmbCardWorkAs.AddObject(TTextItem.Create(cmbCardWorkAs, '速攻魔法'));
  cmbCardWorkAs.AddObject(TTextItem.Create(cmbCardWorkAs, '永续魔法'));
  cmbCardWorkAs.AddObject(TTextItem.Create(cmbCardWorkAs, '仪式魔法'));
  cmbCardWorkAs.AddObject(TTextItem.Create(cmbCardWorkAs, '通常陷阱'));
  cmbCardWorkAs.AddObject(TTextItem.Create(cmbCardWorkAs, '永续陷阱'));
  cmbCardWorkAs.AddObject(TTextItem.Create(cmbCardWorkAs, '反击陷阱'));

  // load user
  LoadUser();

  // create a default puzzle
  NewPuzzle();

end;

procedure TFormMain.FormDestroy(Sender: TObject);
begin
  if FPuzzle <> nil then begin
    FPuzzle.Free;
  end;
  if FUserInfo <> nil then begin
    FUserInfo.Free;
  end;
end;

procedure TFormMain.LoadCardImage;
var
  AImgPath: string;
begin
  if (FCurrentCard = nil) then Exit;
  if (FCurrentCard.GetPosition = cpDownDef) or (FCurrentCard.GetPosition = cpDown) then begin
    AImgPath := ExtractFilePath(ParamStr(0)) + PathDelim + 'cardimg' + PathDelim + '0.jpg';
  end else begin
    AImgPath := ExtractFilePath(ParamStr(0)) + PathDelim + 'cardimg' + PathDelim + Format('%d.jpg', [FCurrentCard.GetCardId]);
  end;

  case FFieldInfo.Owner of
  foSelf:
    begin
      case FFieldInfo.Location of
      clMZone:
        begin
          ImgMonster[0][FFieldInfo.Seq].Bitmap.LoadFromFile(AImgPath);
          if (FCurrentCard.GetPosition = cpUpDef) or (FCurrentCard.GetPosition = cpDownDef) then begin
            ImgMonster[0][FFieldInfo.Seq].RotationAngle := 90;
          end else begin
            ImgMonster[0][FFieldInfo.Seq].RotationAngle := 0;
          end;
        end;
      clSZone:
        begin
          ImgSpell[0][FFieldInfo.Seq].Bitmap.LoadFromFile(AImgPath);
          ImgSpell[0][FFieldInfo.Seq].RotationAngle := 0;
        end;
      end;
    end;
  foOpponent:
    begin
      case FFieldInfo.Location of
      clMZone:
        begin
          ImgMonster[1][FFieldInfo.Seq].Bitmap.LoadFromFile(AImgPath);
          if (FCurrentCard.GetPosition = cpUpDef) or (FCurrentCard.GetPosition = cpDownDef) then begin
            ImgMonster[1][FFieldInfo.Seq].RotationAngle := 270;
          end else begin
            ImgMonster[1][FFieldInfo.Seq].RotationAngle := 180;
          end;
        end;
      clSZone:
        begin
          ImgSpell[1][FFieldInfo.Seq].Bitmap.LoadFromFile(AImgPath);
          ImgSpell[1][FFieldInfo.Seq].RotationAngle := 180;
        end;
      end;
    end;
  foShare:
    begin
      ImgExtraMonster[FFieldInfo.Seq].Bitmap.LoadFromFile(AImgPath);
      ImgExtraMonster[FFieldInfo.Seq].RotationAngle := 0;
    end;

  end;

end;

procedure TFormMain.LoadUser;
var
  AToken: String;
begin
  // load user
  AToken := TConfig.GetMCToken();
  if (AToken <> '') then begin
    FUserToken:= AToken;
    ThreadEXT.ThreadExecute<String, TMCUser>(AToken, userExec, userTerm);
  end;
end;

function TFormMain.loginExec(ATID: TThreadID; AData: TMCSignInReq): TMCUserWithToken;
var
  acc, pwd: string;
begin
  acc := AData.account;
  pwd := AData.password;
  AData.Free;
  Exit(TAPI.MCLogin(acc, pwd));
end;

procedure TFormMain.loginTerm(ATID: TThreadID; AData: TMCUserWithToken);
begin
  if (AData <> nil) then begin
    FUserToken:= AData.token;
    FUserInfo := TMCUser.Create;
    FUserInfo.name:= AData.user.name;
    FUserInfo.id:= AData.user.id;
    FUserInfo.username:= AData.user.username;
    FUserInfo.email:= AData.user.email;
    FUserInfo.avatar:= AData.user.avatar;
    lblLoginUser.Text:= FUserInfo.username;
    AData.Free;
    // save token
    TConfig.SaveMCToken(FUserToken);
  end else begin
    FUserToken:= '';
  end;
end;

procedure TFormMain.NewPuzzle;
begin
  if (FPuzzle <> nil) then FreeAndNil(FPuzzle);
  FFilePath:= '';
  FPuzzle := TPuzzleField.Create;
  RefreshFieldCards();
end;

procedure TFormMain.OpenPuzzle(AFilePath: string);
var
  AJsonString: string;
begin
  if (FPuzzle <> nil) then FreeAndNil(FPuzzle);
  FFilePath:= AFilePath;
  with TStringList.Create do begin
    LoadFromFile(AFilePath, TEncoding.UTF8);
    AJsonString:= Text;
    Free;
  end;
  FPuzzle := TPuzzleJson.JsonToPuzzle(AJsonString);
  FPuzzle.Optimize();
  RefreshFieldCards();
end;

procedure TFormMain.PerformFieldClick;
begin
  if FCurrentCard = nil then Exit;
  case FCurrentCard.GetOwner of
  coSelf:
    begin
      case FCurrentCard.GetLocation of
      clMZone:
        begin
          if (FCurrentCard.GetSeq >= 5) then begin
            fieldElementClick(BExtraMonster[FCurrentCard.GetSeq]);
          end else begin
            fieldElementClick(BMonster[0][FCurrentCard.GetSeq]);
          end;
        end;
      clSZone:
        begin
          fieldElementClick(BSpell[0][FCurrentCard.GetSeq]);
        end;
      end;
    end;
  coOpponent:
    begin
      case FCurrentCard.GetLocation of
      clMZone:
        begin
          if (FCurrentCard.GetSeq >= 5) then begin
            fieldElementClick(BExtraMonster[FCurrentCard.GetSeq]);
          end else begin
            fieldElementClick(BMonster[1][FCurrentCard.GetSeq]);
          end;
        end;
      clSZone:
        begin
          fieldElementClick(BSpell[1][FCurrentCard.GetSeq]);
        end;
      end;
    end;
  end;

end;

procedure TFormMain.ReEquipTo;
var
  i: Integer;
  c: ICard;
  tmpList: TArray<ICard>;
  idx: Integer;
begin
  idx := -1;

  if (FCurrentCard = nil) then Exit;

  SetLength(tmpList, 0);
  //
  cmbCardEquipTo.Clear;
  cmbCardEquipTo.BeginUpdate;

  // add "none"
  cmbCardEquipTo.AddObject(TCardEquipToItem.Create(cmbCardEquipTo, nil));

  // self
  for i := 0 to 4 do begin
    c := FPuzzle.MCard[0, i];
    if (c <> nil) then begin
      cmbCardEquipTo.AddObject(TCardEquipToItem.Create(cmbCardEquipTo, c));
      tmpList.Add(c);
    end;
  end;
  // shared
  for i := 5 to 6 do begin
    c := FPuzzle.ExtraMonster[i];
    if (c <> nil) then begin
      cmbCardEquipTo.AddObject(TCardEquipToItem.Create(cmbCardEquipTo, c));
      tmpList.Add(c);
    end;
  end;

  // opponent
  for i := 0 to 4 do begin
    c := FPuzzle.MCard[1, i];
    if (c <> nil) then begin
      cmbCardEquipTo.AddObject(TCardEquipToItem.Create(cmbCardEquipTo, c));
      tmpList.Add(c);
    end;
  end;

  cmbCardEquipTo.EndUpdate;

  if (FCurrentCard.GetEquipTo <> nil) then begin
    for i := 0 to tmpList.Count - 1 do begin
      c := tmpList[i];
      if (c.GetCardId = FCurrentCard.GetEquipTo.GetCardId) and (c.GetOwner = FCurrentCard.GetEquipTo.GetOwner) and (c.GetPlayer = FCurrentCard.GetEquipTo.GetPlayer) and (c.GetPosition = FCurrentCard.GetEquipTo.GetPosition) and (c.GetSeq = FCurrentCard.GetEquipTo.GetSeq) then begin
        idx:= i;
        Break;
      end;
    end;
  end;
  cmbCardEquipTo.ItemIndex:= idx + 1;

  SetLength(tmpList, 0);
end;

procedure TFormMain.RefreshCardList;
var
  i: Integer;
  item: TCardOperItem;
begin
  if not (FFieldInfo.Location in [clDeck, clHand, clExtra, clGrave, clRemoved]) then Exit;
  FCurrentCardList := nil;
  case FFieldInfo.Owner of
  foSelf:
    begin
      case FFieldInfo.Location of
      clHand: FCurrentCardList := FPuzzle.P0Hand;
      clDeck: FCurrentCardList := FPuzzle.P0Deck;
      clExtra: FCurrentCardList := FPuzzle.P0ExtraDeck;
      clGrave: FCurrentCardList := FPuzzle.P0Grave;
      clRemoved: FCurrentCardList := FPuzzle.P0Removed;
      end;
    end;
  foOpponent:
    begin
      case FFieldInfo.Location of
      clHand: FCurrentCardList := FPuzzle.P1Hand;
      clDeck: FCurrentCardList := FPuzzle.P1Deck;
      clExtra: FCurrentCardList := FPuzzle.P1ExtraDeck;
      clGrave: FCurrentCardList := FPuzzle.P1Grave;
      clRemoved: FCurrentCardList := FPuzzle.P1Removed;
      end;
    end;
  end;
  if (FCurrentCardList = nil) then Exit;
  lstCardList.Clear;
  lstCardList.BeginUpdate;
  for  i:= 0 to FCurrentCardList.Count - 1 do begin
    item := TCardOperItem.Create(lstCardList);
    item.CardName:= FCurrentCardList[i].GetCardName;
    item.CardId:= FCurrentCardList[i].GetCardId;
    item.Card := FCurrentCardList[i];
    item.Index:= i;
    if (FFieldInfo.Location = clGrave) then begin
      // only face up
      item.Face := 0;
      item.ShowFace := False;
    end else begin
      item.ShowFace := True;
      case FCurrentCardList[i].GetPosition of
      cpUpAtk, cpUpDef, cpUp: item.Face := 0;
      cpDownDef, cpDown: item.Face := 1;
      end;
    end;;
    item.OnDelete:= currentCardListDelete;
    item.OnCardAddOneClicked:= currentCardListAddOne;
  end;
  lstCardList.EndUpdate;
end;

procedure TFormMain.RefreshFieldCards;
var
  i, j: Integer;
  c: ICard;
  AImgPath: string;
begin
  // refresh field cards
  FCurrentCard := nil;
  for i := 0 to 1 do begin
    for j := 0 to 4 do begin
      c := FPuzzle.MCard[i, j];
      if (c <> nil) then begin
        if c.GetPosition = cpDownDef then begin
          AImgPath:= ExtractFilePath(ParamStr(0)) + PathDelim + 'cardimg' + PathDelim + '0.jpg';
        end else begin
          AImgPath:= ExtractFilePath(ParamStr(0)) + PathDelim + 'cardimg' + PathDelim + Format('%d.jpg', [c.GetCardId]);
        end;
        ImgMonster[i, j].Bitmap.LoadFromFile(AImgPath);
        if (c.GetPosition = cpUpDef) or (c.GetPosition = cpDownDef) then begin
          if (i = 0) then begin
            ImgMonster[i, j].RotationAngle := 90;
          end else begin
            ImgMonster[i, j].RotationAngle := 270;
          end;
        end else begin
          if (i = 0) then begin
            ImgMonster[i, j].RotationAngle := 0;
          end else begin
            ImgMonster[i, j].RotationAngle := 180;
          end;
        end;
      end else begin
        ImgMonster[i, j].Bitmap.Clear(TAlphaColors.Null);
      end;
    end;
  end;
  for i := 0 to 1 do begin
    for j := 0 to 5 do begin
      c := FPuzzle.SCard[i, j];
      if (c <> nil) then begin
        if c.GetPosition = cpDown then begin
          AImgPath:= ExtractFilePath(ParamStr(0)) + PathDelim + 'cardimg' + PathDelim + '0.jpg';
        end else begin
          AImgPath:= ExtractFilePath(ParamStr(0)) + PathDelim + 'cardimg' + PathDelim + Format('%d.jpg', [c.GetCardId]);
        end;
        ImgSpell[i, j].Bitmap.LoadFromFile(AImgPath);
        if (i = 0) then begin
          ImgSpell[i, j].RotationAngle := 0;
        end else begin
          ImgSpell[i, j].RotationAngle := 180;
        end;
      end else begin
        ImgSpell[i, j].Bitmap.Clear(TAlphaColors.Null);
      end;
    end;
  end;
  for i := 5 to 6 do begin
    c := FPuzzle.ExtraMonster[i];
    if (c <> nil) then begin
      AImgPath:= ExtractFilePath(ParamStr(0)) + PathDelim + 'cardimg' + PathDelim + Format('%d.jpg', [c.GetCardId]);
      ImgExtraMonster[i].Bitmap.LoadFromFile(AImgPath);
      if (c.GetOwner = coSelf) then begin
        if (c.GetIsLinkMonster) then begin
          ImgExtraMonster[i].RotationAngle := 0;
        end else begin
          if (c.GetPosition = cpUpDef) then begin
            ImgExtraMonster[i].RotationAngle := 90;
          end else if (c.GetPosition = cpDownDef) then begin
            AImgPath:= ExtractFilePath(ParamStr(0)) + PathDelim + 'cardimg' + PathDelim + '0.jpg';
            ImgExtraMonster[i].Bitmap.LoadFromFile(AImgPath);
            ImgExtraMonster[i].RotationAngle := 90;
          end;
        end;
      end else begin
        if (c.GetIsLinkMonster) then begin
          ImgExtraMonster[i].RotationAngle := 180;
        end else begin
          if (c.GetPosition = cpUpDef) then begin
            ImgExtraMonster[i].RotationAngle := 270;
          end else if (c.GetPosition = cpDownDef) then begin
            AImgPath:= ExtractFilePath(ParamStr(0)) + PathDelim + 'cardimg' + PathDelim + '0.jpg';
            ImgExtraMonster[i].Bitmap.LoadFromFile(AImgPath);
            ImgExtraMonster[i].RotationAngle := 270;
          end;
        end;
      end;
    end else begin
      ImgExtraMonster[i].Bitmap.Clear(TAlphaColors.Null);
    end;
  end;

  btnP0Hand.SetFocus;
  fieldElementClick(btnP0Hand);
end;

procedure TFormMain.SavePuzzle;
begin
  if (FFilePath = '') then begin
    with TSaveDialog.Create(Self) do begin
      Filter:= 'JSON|*.json';
      DefaultExt:= 'json';
      if (Execute) then begin
        FFilePath:= FileName;
      end;
      Free;
    end;
  end;
  if (FFilePath <> '') then begin
    FPuzzle.Optimize();
    with TStringList.Create do begin
      Text:= TPuzzleJson.PuzzleToJson(FPuzzle);
      WriteBOM := False;
      SaveToFile(FFilePath, TEncoding.UTF8);
      Free;
    end;
  end;
end;

procedure TFormMain.UnloadCardImage;
begin
  case FFieldInfo.Owner of
  foSelf:
    begin
      case FFieldInfo.Location of
      clMZone: ImgMonster[0][FFieldInfo.Seq].Bitmap.Clear(TAlphaColors.Null);
      clSZone: ImgSpell[0][FFieldInfo.Seq].Bitmap.Clear(TAlphaColors.Null);
      end;
    end;
  foOpponent:
    begin
      case FFieldInfo.Location of
      clMZone: ImgMonster[1][FFieldInfo.Seq].Bitmap.Clear(TAlphaColors.Null);
      clSZone: ImgSpell[1][FFieldInfo.Seq].Bitmap.Clear(TAlphaColors.Null);
      end;
    end;
  foShare:
    begin
      ImgExtraMonster[FFieldInfo.Seq].Bitmap.Clear(TAlphaColors.Null);
    end;
  end;
end;

procedure TFormMain.UploadPuzzle;
var
  APuzzleCode: string;
  AP: TEntityPuzzle;
  ARet: TSavePuzzleResult;
begin
  // upload puzzle
  if FPuzzle = nil then Exit;
  if FUserInfo = nil then Exit;
  if FUserInfo.id = 0 then Exit;
  if FUserToken = '' then Exit;
  if FFilePath = '' then Exit;

  if (not FPuzzle.IsValid) then Exit;

  btnUpload.Enabled:= False;

  APuzzleCode:= TScriptGenerator.Generate(FPuzzle);
  AP := TEntityPuzzle.Create;
  AP.id:= FPuzzle.PuzzleId;
  AP.name:= FPuzzle.PuzzleName;
  AP.userId:= FUserInfo.id;
  AP.contributor:= FUserInfo.username;
  AP.message:= FPuzzle.Message;
  AP.solution:= FPuzzle.Solution;
  AP.coverCard:= GetFirstCardFromScriptCode(APuzzleCode);
  AP.luaScript:= APuzzleCode;
  ARet := TAPI.SavePuzzle(AP, FUserInfo.id, FUserToken);
  AP.Free;
  if ARet <> nil then begin
    if ARet.Message = 'false' then begin
      TDialogService.MessageDialog('上传残局失败, 请联系管理员', TMsgDlgType.mtInformation, [TMsgDlgBtn.mbOK], TMsgDlgBtn.mbOK, 0, nil);
    end else begin;
      FPuzzle.PuzzleId:= Aret.Data;
      SavePuzzle();
      TDialogService.MessageDialog('上传残局成功', TMsgDlgType.mtInformation, [TMsgDlgBtn.mbOK], TMsgDlgBtn.mbOK, 0, nil);
    end;
    ARet.Free;
  end else begin
    TDialogService.MessageDialog('上传残局失败, 请联系管理员', TMsgDlgType.mtInformation, [TMsgDlgBtn.mbOK], TMsgDlgBtn.mbOK, 0, nil);
  end;
  btnUpload.Enabled:= True;
end;

function TFormMain.userExec(ATID: TThreadID; AData: String): TMCUser;
begin
  Exit(TAPI.MCValidate(AData));
end;

procedure TFormMain.userTerm(ATID: TThreadID; AData: TMCUser);
begin
  if (AData <> nil) then begin
    FUserInfo := AData;
    if FUserInfo <> nil then begin
      lblLoginUser.Text:= FUserInfo.username;
    end else begin
      FUserToken:= '';
    end;
  end;
end;

end.
