unit frmHome;

{$mode Delphi}{$H+}

interface

uses
  Windows, Classes, SysUtils, Forms, Controls, Graphics, Dialogs, orca_scene2d, fgl, untData, untConstant, untCard, untUtils,
  untAPI, ShellApi;

type

  { TFormHome }

  TFormHome = class(TForm)
    CalloutPanel1: TD2CalloutPanel;
    btnP0Extra: TD2CornerButton;
    btnP1M4: TD2CornerButton;
    btnP1S4: TD2CornerButton;
    btnP1S1: TD2CornerButton;
    btnP1S2: TD2CornerButton;
    btnP1S3: TD2CornerButton;
    btnP1Extra: TD2CornerButton;
    btnP1S0: TD2CornerButton;
    btnP1S5: TD2CornerButton;
    btnP1M0: TD2CornerButton;
    btnP1M1: TD2CornerButton;
    btnP1M2: TD2CornerButton;
    btnP1M3: TD2CornerButton;
    btnP0M0: TD2CornerButton;
    btnP0S0: TD2CornerButton;
    btnP0M4: TD2CornerButton;
    btnP0M3: TD2CornerButton;
    btnP0M2: TD2CornerButton;
    btnP0M1: TD2CornerButton;
    btnP0Deck: TD2CornerButton;
    btnP0S4: TD2CornerButton;
    btnP0S3: TD2CornerButton;
    btnP0S2: TD2CornerButton;
    btnP0S1: TD2CornerButton;
    btnM6: TD2CornerButton;
    btnM5: TD2CornerButton;
    btnP0Hand: TD2CornerButton;
    btnP1Deck: TD2CornerButton;
    btnP1Hand: TD2CornerButton;
    btnP0S5: TD2CornerButton;
    btnP1Grave: TD2CornerButton;
    btnP1Removed: TD2CornerButton;
    btnP0Removed: TD2CornerButton;
    btnP0Grave: TD2CornerButton;
    btnAddCard: TD2CornerButton;
    btnDelCard: TD2CornerButton;
    chkCardProc: TD2CheckBox;
    cmbCardOwner: TD2ComboBox;
    cmbCardPosition: TD2ComboBox;
    cmbCardEquipTo: TD2ComboBox;
    btnCardContinousEffectTo: TD2CornerButton;
    btnXyzElements: TD2CornerButton;
    btnAddCardToList: TD2CornerButton;
    cmbCardWorkAs: TD2ComboBox;
    GlowEffect1: TD2GlowEffect;
    GlowEffect10: TD2GlowEffect;
    GlowEffect11: TD2GlowEffect;
    GlowEffect12: TD2GlowEffect;
    GlowEffect13: TD2GlowEffect;
    GlowEffect14: TD2GlowEffect;
    GlowEffect15: TD2GlowEffect;
    GlowEffect16: TD2GlowEffect;
    GlowEffect17: TD2GlowEffect;
    GlowEffect18: TD2GlowEffect;
    GlowEffect19: TD2GlowEffect;
    GlowEffect2: TD2GlowEffect;
    GlowEffect20: TD2GlowEffect;
    GlowEffect21: TD2GlowEffect;
    GlowEffect22: TD2GlowEffect;
    GlowEffect23: TD2GlowEffect;
    GlowEffect24: TD2GlowEffect;
    GlowEffect25: TD2GlowEffect;
    GlowEffect26: TD2GlowEffect;
    GlowEffect27: TD2GlowEffect;
    GlowEffect28: TD2GlowEffect;
    GlowEffect29: TD2GlowEffect;
    GlowEffect3: TD2GlowEffect;
    GlowEffect30: TD2GlowEffect;
    GlowEffect31: TD2GlowEffect;
    GlowEffect32: TD2GlowEffect;
    GlowEffect33: TD2GlowEffect;
    GlowEffect34: TD2GlowEffect;
    GlowEffect4: TD2GlowEffect;
    GlowEffect5: TD2GlowEffect;
    GlowEffect6: TD2GlowEffect;
    GlowEffect7: TD2GlowEffect;
    GlowEffect8: TD2GlowEffect;
    GlowEffect9: TD2GlowEffect;
    imgP0S0: TD2Image;
    imgP1M4: TD2Image;
    imgP1M3: TD2Image;
    imgP1M1: TD2Image;
    imgP1M0: TD2Image;
    imgM5: TD2Image;
    imgM6: TD2Image;
    imgP1S5: TD2Image;
    imgP0S5: TD2Image;
    imgP0M0: TD2Image;
    imgP0S1: TD2Image;
    imgP1S4: TD2Image;
    imgP0S2: TD2Image;
    imgP0S3: TD2Image;
    imgP0M4: TD2Image;
    imgP0S4: TD2Image;
    imgP1S3: TD2Image;
    imgP0M2: TD2Image;
    imgP0M3: TD2Image;
    imgP1S2: TD2Image;
    imgP1S1: TD2Image;
    imgP1S0: TD2Image;
    imgP1M2: TD2Image;
    imgP0M1: TD2Image;
    InnerGlowEffect1: TD2InnerGlowEffect;
    InnerGlowEffect10: TD2InnerGlowEffect;
    InnerGlowEffect11: TD2InnerGlowEffect;
    InnerGlowEffect12: TD2InnerGlowEffect;
    InnerGlowEffect13: TD2InnerGlowEffect;
    InnerGlowEffect14: TD2InnerGlowEffect;
    InnerGlowEffect15: TD2InnerGlowEffect;
    InnerGlowEffect16: TD2InnerGlowEffect;
    InnerGlowEffect17: TD2InnerGlowEffect;
    InnerGlowEffect18: TD2InnerGlowEffect;
    InnerGlowEffect19: TD2InnerGlowEffect;
    InnerGlowEffect2: TD2InnerGlowEffect;
    InnerGlowEffect20: TD2InnerGlowEffect;
    InnerGlowEffect21: TD2InnerGlowEffect;
    InnerGlowEffect22: TD2InnerGlowEffect;
    InnerGlowEffect23: TD2InnerGlowEffect;
    InnerGlowEffect24: TD2InnerGlowEffect;
    InnerGlowEffect25: TD2InnerGlowEffect;
    InnerGlowEffect26: TD2InnerGlowEffect;
    InnerGlowEffect27: TD2InnerGlowEffect;
    InnerGlowEffect28: TD2InnerGlowEffect;
    InnerGlowEffect29: TD2InnerGlowEffect;
    InnerGlowEffect3: TD2InnerGlowEffect;
    InnerGlowEffect30: TD2InnerGlowEffect;
    InnerGlowEffect31: TD2InnerGlowEffect;
    InnerGlowEffect32: TD2InnerGlowEffect;
    InnerGlowEffect33: TD2InnerGlowEffect;
    InnerGlowEffect34: TD2InnerGlowEffect;
    InnerGlowEffect4: TD2InnerGlowEffect;
    InnerGlowEffect5: TD2InnerGlowEffect;
    InnerGlowEffect6: TD2InnerGlowEffect;
    InnerGlowEffect7: TD2InnerGlowEffect;
    InnerGlowEffect8: TD2InnerGlowEffect;
    InnerGlowEffect9: TD2InnerGlowEffect;
    Label1: TD2Label;
    Label10: TD2Label;
    lblLoginUser: TD2Label;
    Label2: TD2Label;
    Label3: TD2Label;
    Label4: TD2Label;
    Label5: TD2Label;
    Label6: TD2Label;
    Label7: TD2Label;
    Label8: TD2Label;
    Label9: TD2Label;
    Layout16: TD2Layout;
    Layout17: TD2Layout;
    Layout18: TD2Layout;
    Layout19: TD2Layout;
    Layout20: TD2Layout;
    Layout21: TD2Layout;
    Layout22: TD2Layout;
    Layout23: TD2Layout;
    Layout24: TD2Layout;
    lblCardType: TD2Label;
    Layout15: TD2Layout;
    lblCardName: TD2Label;
    layEditList: TD2Layout;
    layAddCard: TD2Layout;
    layDelCard: TD2Layout;
    layCardInfo: TD2Layout;
    Layout14: TD2Layout;
    edtCardIndicator: TD2NumberBox;
    lstCardList: TD2ListBox;
    Popup1: TD2Popup;
    Popup2: TD2Popup;
    btnAbout: TD2ToolButton;
    Popup3: TD2Popup;
    btnUpload: TD2ToolButton;
    txtFieldName: TD2Label;
    Layout1: TD2Layout;
    Layout10: TD2Layout;
    Layout11: TD2Layout;
    Layout12: TD2Layout;
    Layout13: TD2Layout;
    Layout2: TD2Layout;
    Layout3: TD2Layout;
    Layout4: TD2Layout;
    Layout5: TD2Layout;
    Layout6: TD2Layout;
    Layout7: TD2Layout;
    Layout8: TD2Layout;
    Layout9: TD2Layout;
    Panel2: TD2Panel;
    Res: TD2Resources;
    Panel1: TD2Panel;
    Scene: TD2Scene;
    Root: TD2Background;
    tbMain: TD2ToolBar;
    btnNew: TD2ToolButton;
    btnOpen: TD2ToolButton;
    btnSave: TD2ToolButton;
    btnConfig: TD2ToolButton;
    btnDebug: TD2ToolButton;
    txtOwner: TD2Text;
    procedure btnAboutClick(Sender: TObject);
    procedure btnAddCardClick(Sender: TObject);
    procedure btnAddCardToListClick(Sender: TObject);
    procedure btnCardContinousEffectToClick(Sender: TObject);
    procedure btnConfigClick(Sender: TObject);
    procedure btnDelCardClick(Sender: TObject);
    procedure btnEditCardListClick(Sender: TObject);
    procedure btnDebugClick(Sender: TObject);
    procedure btnNewClick(Sender: TObject);
    procedure btnOpenClick(Sender: TObject);
    procedure btnSaveClick(Sender: TObject);
    procedure btnUploadClick(Sender: TObject);
    procedure btnXyzElementsClick(Sender: TObject);
    procedure chkCardProcChange(Sender: TObject);
    procedure chkCardProcClick(Sender: TObject);
    procedure cmbCardEquipToChange(Sender: TObject);
    procedure cmbCardEquipToKillFocus(Sender: TObject);
    procedure cmbCardOwnerChange(Sender: TObject);
    procedure cmbCardOwnerKillFocus(Sender: TObject);
    procedure cmbCardPositionChange(Sender: TObject);
    procedure cmbCardPositionKillFocus(Sender: TObject);
    procedure cmbCardWorkAsChange(Sender: TObject);
    procedure cmbCardWorkAsKillFocus(Sender: TObject);
    procedure edtCardIndicatorChange(Sender: TObject);
    procedure edtCardIndicatorTyping(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);

    procedure fieldElementClick(Sender: TObject);
    procedure FormShow(Sender: TObject);
  private
    BHand: array[0..1] of TD2CornerButton;
    BDeck: array[0..1] of TD2CornerButton;
    BExtraDeck: array[0..1] of TD2CornerButton;
    BGrave: array[0..1] of TD2CornerButton;
    BRemoved: array[0..1] of TD2CornerButton;
    BMonster: array[0..1, 0..4] of TD2CornerButton;
    BSpell: array[0..1, 0..5] of TD2CornerButton;
    BExtraMonster: array[5..6] of TD2CornerButton;

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

    FSaved: Boolean;
    FFilePath: string;
    FPuzzle: TPuzzleField;
    FCard: TCard;

    FFieldInfo: TFieldRec;
    FCurrentCard: TCard;
    FCurrentCardList: TFPGList<TCard>;

    FUserToken: String;
    FUserInfo: TMCUser;

    function CheckSaved(AIsNew: Boolean): Boolean;
    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

  end;

var
  FormHome: TFormHome;

implementation

uses
  frmSearchCard, frmCardList, frmContinousEffected, untCardJson, untListItem, frmPuzzleConfig, frmAbout,
  untScriptGenerator, frmLogin, untConfig, ISCThread;

{$R *.lfm}

{ TFormHome }

procedure TFormHome.FormCreate(Sender: TObject);
var
  i, j: Integer;
begin
  FPuzzle := nil;
  FSaved:= True;
  FFilePath:= '';
  FCurrentCard := nil;

  // 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;

  ImgMonster[0][0] := imgP0M0;
  ImgMonster[0][1] := imgP0M1;
  ImgMonster[0][2] := imgP0M2;
  ImgMonster[0][3] := imgP0M3;
  ImgMonster[0][4] := imgP0M4;
  ImgMonster[1][0] := imgP1M0;
  ImgMonster[1][1] := imgP1M1;
  ImgMonster[1][2] := imgP1M2;
  ImgMonster[1][3] := imgP1M3;
  ImgMonster[1][4] := imgP1M4;
  ImgSpell[0][0] := imgP0S0;
  ImgSpell[0][1] := imgP0S1;
  ImgSpell[0][2] := imgP0S2;
  ImgSpell[0][3] := imgP0S3;
  ImgSpell[0][4] := imgP0S4;
  ImgSpell[0][5] := imgP0S5;
  ImgSpell[1][0] := imgP1S0;
  ImgSpell[1][1] := imgP1S1;
  ImgSpell[1][2] := imgP1S2;
  ImgSpell[1][3] := imgP1S3;
  ImgSpell[1][4] := imgP1S4;
  ImgSpell[1][5] := imgP1S5;
  ImgExtraMonster[5] := imgM5;
  ImgExtraMonster[6] := imgM6;

  // give text
  for i := 0 to 1 do begin
    for j := 0 to 4 do begin
      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;
  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(TTextItemEx.Create(cmbCardWorkAs, '不改变'));
  cmbCardWorkAs.AddObject(TTextItemEx.Create(cmbCardWorkAs, '通常怪兽'));
  cmbCardWorkAs.AddObject(TTextItemEx.Create(cmbCardWorkAs, '效果怪兽'));
  cmbCardWorkAs.AddObject(TTextItemEx.Create(cmbCardWorkAs, '仪式怪兽'));
  cmbCardWorkAs.AddObject(TTextItemEx.Create(cmbCardWorkAs, '融合怪兽'));
  cmbCardWorkAs.AddObject(TTextItemEx.Create(cmbCardWorkAs, '同调怪兽'));
  cmbCardWorkAs.AddObject(TTextItemEx.Create(cmbCardWorkAs, '超量怪兽'));
  cmbCardWorkAs.AddObject(TTextItemEx.Create(cmbCardWorkAs, '连接怪兽'));
  cmbCardWorkAs.AddObject(TTextItemEx.Create(cmbCardWorkAs, '灵摆怪兽'));
  cmbCardWorkAs.AddObject(TTextItemEx.Create(cmbCardWorkAs, 'Token'));
  cmbCardWorkAs.AddObject(TTextItemEx.Create(cmbCardWorkAs, '通常魔法'));
  cmbCardWorkAs.AddObject(TTextItemEx.Create(cmbCardWorkAs, '装备魔法'));
  cmbCardWorkAs.AddObject(TTextItemEx.Create(cmbCardWorkAs, '场地魔法'));
  cmbCardWorkAs.AddObject(TTextItemEx.Create(cmbCardWorkAs, '速攻魔法'));
  cmbCardWorkAs.AddObject(TTextItemEx.Create(cmbCardWorkAs, '永续魔法'));
  cmbCardWorkAs.AddObject(TTextItemEx.Create(cmbCardWorkAs, '仪式魔法'));
  cmbCardWorkAs.AddObject(TTextItemEx.Create(cmbCardWorkAs, '通常陷阱'));
  cmbCardWorkAs.AddObject(TTextItemEx.Create(cmbCardWorkAs, '永续陷阱'));
  cmbCardWorkAs.AddObject(TTextItemEx.Create(cmbCardWorkAs, '反击陷阱'));

  // load user
  LoadUser();

  // create a default puzzle
  NewPuzzle();
end;

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

procedure TFormHome.fieldElementClick(Sender: TObject);
var
  n: string;
  fieldInfo: TFieldRec;
  fieldStr: TFieldStrings;
  card: TCard;
  cardCount: Integer = 0;
begin
  n := TD2CornerButton(Sender).Name;
  fieldInfo := ElementNameToFieldRec(n);
  fieldStr := ElementNameToFieldStrings(fieldInfo);
  txtOwner.Text:= fieldStr.Owner;
  txtOwner.Fill.Color:= 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.CardName, FCurrentCard.CardId]);
        lblCardType.Text:= CardTypeToReadable(FCurrentCard.CardType);
        cmbCardOwner.ItemIndex:= IfThen<Integer>(FCurrentCard.Owner = coSelf, 0, 1);
        // card position
        cmbCardPosition.Clear;

        case card.Location of
        clMZone:
          begin
            if (card.IsLinkMonster) 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.Position of
              cpUpAtk: cmbCardPosition.ItemIndex:= 0;
              cpUpDef: cmbCardPosition.ItemIndex:= 1;
              cpDownDef: cmbCardPosition.ItemIndex:= 2;
              end;
            end;
          end;
        clSZone:
          begin
            if (card.CardType = 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.Position of
              cpUp: cmbCardPosition.ItemIndex:= 0;
              cpDown: cmbCardPosition.ItemIndex:=1;
              end;
            end;
          end;
        end;

        // load details
        chkCardProc.IsChecked:= card.Proc;
        edtCardIndicator.Value:= card.IndicatorCount;

        ReEquipTo();
        btnCardContinousEffectTo.Text:= Format('共 %d 张卡', [card.ContinousEffectTo.Count]);
        btnXyzElements.Enabled:= card.IsXyzMonster;
        if (card.IsXyzMonster) then begin
          btnXyzElements.Text:= Format('共 %d 张卡', [card.XyzElements.Count]);
        end else begin
          btnXyzElements.Text := '非超量怪兽';
        end;

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

procedure TFormHome.FormShow(Sender: TObject);
begin
  //
  Self.Width:= Trunc(Panel2.Position.X + Panel2.Width + 32);
  Self.Height:= Trunc(Panel2.Position.Y + Panel2.Height + 32);
end;

function TFormHome.CheckSaved(AIsNew: Boolean): Boolean;
var
  AAct: string;
  AMsgRet: TModalResult;
begin
  if FSaved then begin
    Exit(True);
  end else begin
    if AIsNew then begin
      AAct:= '新建';
    end else begin
      AAct:= '打开';
    end;
    AMsgRet := MessageDlg('确认?', '当前还有未保存的内容，确定要' + AAct + '吗?', mtConfirmation, mbOKCancel, 0);
    Exit(AMsgRet = mrOK);
  end;
end;

procedure TFormHome.currentCardListDelete(ACardId: Int64; AIndex: Integer);
var
  i: Integer;
  item: TCardListItemOp;
begin
  if (FCurrentCardList = nil) then Exit;

  // index matches
  if (FCurrentCardList[AIndex].CardId = ACardId) then begin
    FCurrentCardList.Items[AIndex].Free;
    FCurrentCardList.Delete(AIndex);
    lstCardList.Items[AIndex].Free;
    Exit;
  end;

  // index not matches
  for i := 0 to FCurrentCardList.Count - 1 do begin
    if (FCurrentCardList[i].CardId = ACardId) then begin
      FCurrentCardList[i].Free;
      FCurrentCardList.Delete(i);
      lstCardList.Items[i].Free;
      Break;
    end;
  end;

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

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

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

procedure TFormHome.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);
      SaveToFile(FFilePath);
      Free;
    end;
    FSaved:= True;
  end;
end;

procedure TFormHome.RefreshFieldCards();
var
  i, j: Integer;
  c: TCard;
  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.Position = cpDownDef then begin
          AImgPath:= ExtractFilePath(ParamStr(0)) + DirectorySeparator + 'cardimg' + DirectorySeparator + '0.jpg';
        end else begin
          AImgPath:= ExtractFilePath(ParamStr(0)) + DirectorySeparator + 'cardimg' + DirectorySeparator + Format('%d.jpg', [c.CardId]);
        end;
        ImgMonster[i, j].Bitmap.LoadFromFile(AImgPath);
        if (c.Position = cpUpDef) or (c.Position = cpDownDef) then begin
          if (i = 0) then begin
            ImgMonster[i, j].RotateAngle := 90;
          end else begin
            ImgMonster[i, j].RotateAngle := 270;
          end;
        end else begin
          if (i = 0) then begin
            ImgMonster[i, j].RotateAngle := 0;
          end else begin
            ImgMonster[i, j].RotateAngle := 180;
          end;
        end;
      end else begin
        ImgMonster[i, j].Bitmap.Clear();
      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.Position = cpDown then begin
          AImgPath:= ExtractFilePath(ParamStr(0)) + DirectorySeparator + 'cardimg' + DirectorySeparator + '0.jpg';
        end else begin
          AImgPath:= ExtractFilePath(ParamStr(0)) + DirectorySeparator + 'cardimg' + DirectorySeparator + Format('%d.jpg', [c.CardId]);
        end;
        ImgSpell[i, j].Bitmap.LoadFromFile(AImgPath);
        if (i = 0) then begin
          ImgSpell[i, j].RotateAngle := 0;
        end else begin
          ImgSpell[i, j].RotateAngle := 180;
        end;
      end else begin
        ImgSpell[i, j].Bitmap.Clear();
      end;
    end;
  end;
  for i := 5 to 6 do begin
    c := FPuzzle.ExtraMonster[i];
    if (c <> nil) then begin
      AImgPath:= ExtractFilePath(ParamStr(0)) + DirectorySeparator + 'cardimg' + DirectorySeparator + Format('%d.jpg', [c.CardId]);
      ImgExtraMonster[i].Bitmap.LoadFromFile(AImgPath);
      if (c.Owner = coSelf) then begin
        if (c.IsLinkMonster) then begin
          ImgExtraMonster[i].RotateAngle := 0;
        end else begin
          if (c.Position = cpUpDef) then begin
            ImgExtraMonster[i].RotateAngle := 90;
          end else if (c.Position = cpDownDef) then begin
            AImgPath:= ExtractFilePath(ParamStr(0)) + DirectorySeparator + 'cardimg' + DirectorySeparator + '0.jpg';
            ImgExtraMonster[i].Bitmap.LoadFromFile(AImgPath);
            ImgExtraMonster[i].RotateAngle := 90;
          end;
        end;
      end else begin
        if (c.IsLinkMonster) then begin
          ImgExtraMonster[i].RotateAngle := 180;
        end else begin
          if (c.Position = cpUpDef) then begin
            ImgExtraMonster[i].RotateAngle := 270;
          end else if (c.Position = cpDownDef) then begin
            AImgPath:= ExtractFilePath(ParamStr(0)) + DirectorySeparator + 'cardimg' + DirectorySeparator + '0.jpg';
            ImgExtraMonster[i].Bitmap.LoadFromFile(AImgPath);
            ImgExtraMonster[i].RotateAngle := 270;
          end;
        end;
      end;
    end else begin
      ImgExtraMonster[i].Bitmap.Clear();
    end;
  end;

  btnP0Hand.SetFocus;
  fieldElementClick(btnP0Hand);
end;

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

procedure TFormHome.LoadCardImage();
var
  AImgPath: string;
begin
  if (FCurrentCard = nil) then Exit;
  if (FCurrentCard.Position = cpDownDef) or (FCurrentCard.Position = cpDown) then begin
    AImgPath := ExtractFilePath(ParamStr(0)) + DirectorySeparator + 'cardimg' + DirectorySeparator + '0.jpg';
  end else begin
    AImgPath := ExtractFilePath(ParamStr(0)) + DirectorySeparator + 'cardimg' + DirectorySeparator + Format('%d.jpg', [FCurrentCard.CardId]);
  end;
  case FFieldInfo.Owner of
  foSelf:
    begin
      case FFieldInfo.Location of
      clMZone:
        begin
          ImgMonster[0][FFieldInfo.Seq].Bitmap.LoadFromFile(AImgPath);
          if (FCurrentCard.Position = cpUpDef) or (FCurrentCard.Position = cpDownDef) then begin
            ImgMonster[0][FFieldInfo.Seq].RotateAngle := 90;
          end else begin
            ImgMonster[0][FFieldInfo.Seq].RotateAngle := 0;
          end;
        end;
      clSZone:
        begin
          ImgSpell[0][FFieldInfo.Seq].Bitmap.LoadFromFile(AImgPath);
          ImgSpell[0][FFieldInfo.Seq].RotateAngle := 0;
        end;
      end;
    end;
  foOpponent:
    begin
      case FFieldInfo.Location of
      clMZone:
        begin
          ImgMonster[1][FFieldInfo.Seq].Bitmap.LoadFromFile(AImgPath);
          if (FCurrentCard.Position = cpUpDef) or (FCurrentCard.Position = cpDownDef) then begin
            ImgMonster[1][FFieldInfo.Seq].RotateAngle := 270;
          end else begin
            ImgMonster[1][FFieldInfo.Seq].RotateAngle := 180;
          end;
        end;
      clSZone:
        begin
          ImgSpell[1][FFieldInfo.Seq].Bitmap.LoadFromFile(AImgPath);
          ImgSpell[1][FFieldInfo.Seq].RotateAngle := 180;
        end;
      end;
    end;
  foShare:
    begin
      ImgExtraMonster[FFieldInfo.Seq].Bitmap.LoadFromFile(AImgPath);
      ImgExtraMonster[FFieldInfo.Seq].RotateAngle := 0;
    end;
  end;
end;

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

procedure TFormHome.ReEquipTo();
var
  i: Integer;
  c: TCard;
  tmpList: TFPGList<TCard>;
  idx: Integer = -1;
begin
  if (FCurrentCard = nil) then Exit;

  tmpList := TFPGList<TCard>.Create;
  //
  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.EquipTo <> nil) then begin
    for i := 0 to tmpList.Count - 1 do begin
      c := tmpList[i];
      if (c.CardId = FCurrentCard.EquipTo.CardId) and (c.Owner = FCurrentCard.EquipTo.Owner) and (c.Player = FCurrentCard.EquipTo.Player) and (c.Position = FCurrentCard.EquipTo.Position) and (c.Seq = FCurrentCard.EquipTo.Seq) then begin
        idx:= i;
        Break;
      end;
    end;
  end;
  cmbCardEquipTo.ItemIndex:= idx + 1;

  tmpList.Free;
end;

procedure TFormHome.RefreshCardList();
var
  i: Integer;
  item: TCardListItemOp;
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 := TCardListItemOp.Create(lstCardList);
    item.CardName:= FCurrentCardList[i].CardName;
    item.Id:= FCurrentCardList[i].CardId;
    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].Position of
      cpUpAtk, cpUpDef, cpUp: item.Face := 0;
      cpDownDef, cpDown: item.Face := 1;
      end;
    end;;
    item.OnDelete:= currentCardListDelete;
  end;
  lstCardList.EndUpdate;
end;

procedure TFormHome.LoadUser();
var
  AToken: String;
begin
  // load user
  AToken := TConfigReader.GetMCToken();
  if (AToken <> '') then begin
    FUserToken:= AToken;
    ISCThreadExecuteGUI<String, TMCUser>(AToken, userExec, userTerm);
  end;
end;

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

procedure TFormHome.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;

function TFormHome.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 TFormHome.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
    TConfigReader.SaveMCToken(FUserToken);
  end else begin
    FUserToken:= '';
  end;
end;

procedure TFormHome.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;

  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);
  if ARet <> nil then begin
    FPuzzle.PuzzleId:= Aret.data;
    SavePuzzle();
    ARet.Free;
    MessageDlg('提示', '上传残局成功', mtConfirmation, [mbOK], 0);
  end else begin
    MessageDlg('提示', '上传残局失败', mtError, [mbOK], 0);
  end;
  btnUpload.Enabled:= True;
end;

procedure TFormHome.btnNewClick(Sender: TObject);
begin
  // new
  if (not CheckSaved(True)) then Exit;
  NewPuzzle();
end;

procedure TFormHome.btnConfigClick(Sender: TObject);
begin
  // config
  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 TFormHome.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 TFormHome.btnAddCardClick(Sender: TObject);
var
  cid: Int64 = 0;
  c: TCard = nil;
begin
  // add card
  with TFormSearchCard.Create(Self) do begin
    if ShowModal = mrOK then begin
      cid:= CardId;
    end;
    Free;
  end;
  c := Cards.GetCardInfo(cid);
  if c <> nil then begin
    FCurrentCard := c;
    case FFieldInfo.Owner of
    foSelf:
      begin
        c.Owner:= coSelf;
        c.Player:= cpSelf;
        case FFieldInfo.Location of
        clMZone:
          begin
            FPuzzle.MCard[0, FFieldInfo.Seq] := c;
            c.Location:= clMZone;
            c.Position:= cpUpAtk;
          end;
        clSZone:
          begin
            FPuzzle.SCard[0, FFieldInfo.Seq] := c;
            c.Location:= clSZone;
            c.Position:= cpUp;
          end;
        end;
        c.Seq := FFieldInfo.Seq;
      end;
    foOpponent:
      begin
        c.Owner:= coOpponent;
        c.Player:= cpOpponent;
        case FFieldInfo.Location of
        clMZone:
          begin
            FPuzzle.MCard[1, FFieldInfo.Seq] := c;
            c.Location:= clMZone;
            c.Position:= cpUpAtk;
          end;
        clSZone:
          begin
            FPuzzle.SCard[1, FFieldInfo.Seq] := c;
            c.Location:= clSZone;
            c.Position:= cpUp;
          end;
        end;
        c.Seq := FFieldInfo.Seq;
      end;
    foShare:
      begin
        c.Owner:= coSelf;
        c.Player:= cpSelf;
        FPuzzle.ExtraMonster[FFieldInfo.Seq] := c;
        c.Location:= clMZone;
        c.Position:= cpUpAtk;
        c.Seq := FFieldInfo.Seq;
      end;
    end;
    lblCardName.Text:= Format('%s (%d)', [FCurrentCard.CardName, FCurrentCard.CardId]);
    layAddCard.Visible:= False;
    layDelCard.Visible:= True;
    layCardInfo.Visible:= True;
    // load card image
    LoadCardImage();
    PerformFieldClick();
  end;
end;

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

procedure TFormHome.btnAddCardToListClick(Sender: TObject);
var
  cid: Int64 = 0;
  item: TCardListItemOp;
  c: TCard;
begin
  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 := Cards.GetCardInfo(cid);
    FCurrentCardList.Add(c);
    item := TCardListItemOp.Create(lstCardList);
    item.CardName:= c.CardName;
    item.Id:= cid;
    item.Card := c;
    item.Index:= lstCardList.Count - 1;
    item.OnDelete:= currentCardListDelete;
    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 TFormHome.btnCardContinousEffectToClick(Sender: TObject);
begin
  // continous effect to
  with TFormContinousEffected.Create(Self) do begin
    CEPuzzle := FPuzzle;
    CECurrentCard := FCurrentCard;
    RefreshList();
    if ShowModal = mrOK then begin
      FCurrentCard.ContinousEffectTo.Clear;
      FCurrentCard.ContinousEffectTo.AddList(CESelectedCards);
      btnCardContinousEffectTo.Text:= Format('共 %d 张卡', [FCurrentCard.ContinousEffectTo.Count]);
    end;
    Free;
  end;
end;

procedure TFormHome.btnEditCardListClick(Sender: TObject);
var
  cardIds: TFPGList<Int64>;
  i: Integer;
  cardCount: Integer = 0;
begin
  cardIds:= TFPGList<Int64>.Create;
  case FFieldInfo.Owner of
  foSelf:
    begin
      case FFieldInfo.Location of
      clHand: for i := 0 to FPuzzle.P0Hand.Count - 1 do cardIds.Add(FPuzzle.P0Hand[i].CardId);
      clDeck: for i := 0 to FPuzzle.P0Deck.Count - 1 do cardIds.Add(FPuzzle.P0Deck[i].CardId);
      clExtra: for i := 0 to FPuzzle.P0ExtraDeck.Count - 1 do cardIds.Add(FPuzzle.P0ExtraDeck[i].CardId);
      clGrave: for i := 0 to FPuzzle.P0Grave.Count - 1 do cardIds.Add(FPuzzle.P0Grave[i].CardId);
      clRemoved: for i := 0 to FPuzzle.P0Removed.Count - 1 do cardIds.Add(FPuzzle.P0Removed[i].CardId);
      end;
    end;
  foOpponent:
    begin
      case FFieldInfo.Location of
      clHand: for i := 0 to FPuzzle.P1Hand.Count - 1 do cardIds.Add(FPuzzle.P1Hand[i].CardId);
      clDeck: for i := 0 to FPuzzle.P1Deck.Count - 1 do cardIds.Add(FPuzzle.P1Deck[i].CardId);
      clExtra: for i := 0 to FPuzzle.P1ExtraDeck.Count - 1 do cardIds.Add(FPuzzle.P1ExtraDeck[i].CardId);
      clGrave: for i := 0 to FPuzzle.P1Grave.Count - 1 do cardIds.Add(FPuzzle.P1Grave[i].CardId);
      clRemoved: for i := 0 to FPuzzle.P1Removed.Count - 1 do cardIds.Add(FPuzzle.P1Removed[i].CardId);
      end;
    end;
  end;

  // edit card list
  with TFormCardList.Create(Self) do begin
    FillCardIds(cardIds);
    if ShowModal = mrOK then begin
      // CardList;
      case FFieldInfo.Owner of
      foSelf:
        begin
          case FFieldInfo.Location of
          clHand:
            begin
              ClearCards(FPuzzle.P0Hand);
              for i:= 0 to CardList.Count - 1 do FPuzzle.P0Hand.Add(Cards.GetCardInfo(CardList[i]));
            end;
          clDeck:
            begin
              ClearCards(FPuzzle.P0Deck);
              for i := 0 to CardList.Count - 1 do FPuzzle.P0Deck.Add(Cards.GetCardInfo(CardList[i]));
            end;
          clExtra:
            begin
              ClearCards(FPuzzle.P0ExtraDeck);
              for i := 0 to CardList.Count - 1 do FPuzzle.P0ExtraDeck.Add(Cards.GetCardInfo(CardList[i]));
            end;
          clGrave:
            begin
              ClearCards(FPuzzle.P0Grave);
              for i := 0 to CardList.Count - 1 do FPuzzle.P0Grave.Add(Cards.GetCardInfo(CardList[i]));
            end;
          clRemoved:
            begin
              ClearCards(FPuzzle.P0Removed);
              for i := 0 to CardList.Count - 1 do FPuzzle.P0Removed.Add(Cards.GetCardInfo(CardList[i]));
            end;
          end;
        end;
      foOpponent:
        begin
          case FFieldInfo.Location of
          clHand:
            begin
              ClearCards(FPuzzle.P1Hand);
              for i:= 0 to CardList.Count - 1 do FPuzzle.P1Hand.Add(Cards.GetCardInfo(CardList[i]));
            end;
          clDeck:
            begin
              ClearCards(FPuzzle.P1Deck);
              for i := 0 to CardList.Count - 1 do FPuzzle.P1Deck.Add(Cards.GetCardInfo(CardList[i]));
            end;
          clExtra:
            begin
              ClearCards(FPuzzle.P1ExtraDeck);
              for i := 0 to CardList.Count - 1 do FPuzzle.P1ExtraDeck.Add(Cards.GetCardInfo(CardList[i]));
            end;
          clGrave:
            begin
              ClearCards(FPuzzle.P1Grave);
              for i := 0 to CardList.Count - 1 do FPuzzle.P1Grave.Add(Cards.GetCardInfo(CardList[i]));
            end;
          clRemoved:
            begin
              ClearCards(FPuzzle.P1Removed);
              for i := 0 to CardList.Count - 1 do FPuzzle.P1Removed.Add(Cards.GetCardInfo(CardList[i]));
            end;
          end;
        end;
      end;
      cardCount := CardList.Count;
      // btnEditCardList.Text:= Format('共 %d 张卡', [cardCount]);
    end;
    Free;
  end;
  cardIds.Free;
end;

procedure TFormHome.btnDebugClick(Sender: TObject);
const
  DEBUG_NAME = '._debug_';
var
  ADir: string;
  ALuaFileName: string;
  ACode: string;
  AMDPro3Path: string;
begin
  SavePuzzle();
  if (FFilePath = '') then Exit;
  ADir:= TConfigReader.GetMDPro3InstallPath();
  ALuaFileName:= ADir + DirectorySeparator + 'Puzzle' + DirectorySeparator + DEBUG_NAME + '.lua';
  ACode:= TScriptGenerator.Generate(FPuzzle);
  with TStringList.Create do begin
    Text:= ACode;
    SaveToFile(ALuaFileName);
    Free;
  end;

  // debug
  AMDPro3Path:= ADir + DirectorySeparator + 'MDPro3.exe';
  ShellExecute(0, 'open', PChar(AMDPro3Path), PChar(Format('-s %s', [DEBUG_NAME])), PChar(ADir), SW_SHOWNORMAL);
end;

procedure TFormHome.btnOpenClick(Sender: TObject);
begin
  // open
  if (not CheckSaved(False)) then Exit;
  with TOpenDialog.Create(Self) do begin
    Filter:= 'JSON|*.json';
    if (Execute) then begin
      OpenPuzzle(FileName);
    end;
    Free;
  end;
end;

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

procedure TFormHome.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;
        req.account:= UserAccount;
        req.password:= UserPassword;
        ISCThreadExecuteGUI<TMCSignInReq, TMCUserWithToken>(req, loginExec, loginTerm);
      end;
      Free;
    end;
    Exit;
  end;
  if (FUserToken <> '') then begin
    UploadPuzzle();
  end;
end;

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

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

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

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

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

procedure TFormHome.cmbCardEquipToKillFocus(Sender: TObject);
begin
  cmbCardEquipTo.Popup.IsOpen:= False;
end;

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

procedure TFormHome.cmbCardOwnerKillFocus(Sender: TObject);
begin
  cmbCardOwner.Popup.IsOpen:= False;
end;

procedure TFormHome.cmbCardPositionChange(Sender: TObject);
var
  AImgPath: string;
begin
  case FCurrentCard.Owner of
  coSelf:
    begin
      case FCurrentCard.Location of
      clMZone:
        begin
          // skip link monsters
          if FCurrentCard.Seq >= 5 then begin
            if FCurrentCard.IsLinkMonster then Exit;
            case cmbCardPosition.ItemIndex of
            0:  // up atk
              begin
                AImgPath:= ExtractFilePath(ParamStr(0)) + DirectorySeparator + 'cardimg' + DirectorySeparator + Format('%d.jpg', [FCurrentCard.CardId]);
                ImgExtraMonster[FCurrentCard.Seq].Bitmap.LoadFromFile(AImgPath);
                ImgExtraMonster[FCurrentCard.Seq].RotateAngle:= 0;
                FCurrentCard.Position:= cpUpAtk;
              end;
            1:  // up def
              begin
                AImgPath:= ExtractFilePath(ParamStr(0)) + DirectorySeparator + 'cardimg' + DirectorySeparator + Format('%d.jpg', [FCurrentCard.CardId]);
                ImgExtraMonster[FCurrentCard.Seq].Bitmap.LoadFromFile(AImgPath);
                ImgExtraMonster[FCurrentCard.Seq].RotateAngle:= 90;
                FCurrentCard.Position:= cpUpDef;
              end;
            2:  // down def
              begin
                AImgPath:= ExtractFilePath(ParamStr(0)) + DirectorySeparator + 'cardimg' + DirectorySeparator + '0.jpg';
                ImgExtraMonster[FCurrentCard.Seq].Bitmap.LoadFromFile(AImgPath);
                ImgExtraMonster[FCurrentCard.Seq].RotateAngle:= 90;
                FCurrentCard.Position:= cpDownDef;
              end;
            end;
            Exit;
          end;
          case cmbCardPosition.ItemIndex of
          0:  // up atk
            begin
              AImgPath:= ExtractFilePath(ParamStr(0)) + DirectorySeparator + 'cardimg' + DirectorySeparator + Format('%d.jpg', [FCurrentCard.CardId]);
              ImgMonster[0][FCurrentCard.Seq].Bitmap.LoadFromFile(AImgPath);
              ImgMonster[0][FCurrentCard.Seq].RotateAngle:= 0;
              FCurrentCard.Position:= cpUpAtk;
            end;
          1:  // up def
            begin
              AImgPath:= ExtractFilePath(ParamStr(0)) + DirectorySeparator + 'cardimg' + DirectorySeparator + Format('%d.jpg', [FCurrentCard.CardId]);
              ImgMonster[0][FCurrentCard.Seq].Bitmap.LoadFromFile(AImgPath);
              ImgMonster[0][FCurrentCard.Seq].RotateAngle:= 90;
              FCurrentCard.Position:= cpUpDef;
            end;
          2:  // down def
            begin
              AImgPath:= ExtractFilePath(ParamStr(0)) + DirectorySeparator + 'cardimg' + DirectorySeparator + '0.jpg';
              ImgMonster[0][FCurrentCard.Seq].Bitmap.LoadFromFile(AImgPath);
              ImgMonster[0][FCurrentCard.Seq].RotateAngle:= 90;
              FCurrentCard.Position:= cpDownDef;
            end;
          end;
        end;
      clSZone:
        begin
          // skip pendulum monsters
          if FCurrentCard.CardType = ctPendulum then Exit;
          case cmbCardPosition.ItemIndex of
          0:  // up
            begin
              AImgPath:= ExtractFilePath(ParamStr(0)) + DirectorySeparator + 'cardimg' + DirectorySeparator + Format('%d.jpg', [FCurrentCard.CardId]);
              ImgSpell[0][FCurrentCard.Seq].Bitmap.LoadFromFile(AImgPath);
              FCurrentCard.Position:= cpUp;
            end;
          1:  // down
            begin
              AImgPath:= ExtractFilePath(ParamStr(0)) + DirectorySeparator + 'cardimg' + DirectorySeparator + '0.jpg';
              ImgSpell[0][FCurrentCard.Seq].Bitmap.LoadFromFile(AImgPath);
              FCurrentCard.Position:= cpDown;
            end;
          end;
        end;
      end;
    end;
  coOpponent:
    begin
      case FCurrentCard.Location of
      clMZone:
        begin
          // skip link monsters
          if FCurrentCard.Seq >= 5 then begin
            if FCurrentCard.IsLinkMonster then Exit;
            case cmbCardPosition.ItemIndex of
            0:  // up atk
              begin
                AImgPath:= ExtractFilePath(ParamStr(0)) + DirectorySeparator + 'cardimg' + DirectorySeparator + Format('%d.jpg', [FCurrentCard.CardId]);
                ImgExtraMonster[FCurrentCard.Seq].Bitmap.LoadFromFile(AImgPath);
                ImgExtraMonster[FCurrentCard.Seq].RotateAngle:= 180;
                FCurrentCard.Position:= cpUpAtk;
              end;
            1:  // up def
              begin
                AImgPath:= ExtractFilePath(ParamStr(0)) + DirectorySeparator + 'cardimg' + DirectorySeparator + Format('%d.jpg', [FCurrentCard.CardId]);
                ImgExtraMonster[FCurrentCard.Seq].Bitmap.LoadFromFile(AImgPath);
                ImgExtraMonster[FCurrentCard.Seq].RotateAngle:= 270;
                FCurrentCard.Position:= cpUpDef;
              end;
            2:  // down def
              begin
                AImgPath:= ExtractFilePath(ParamStr(0)) + DirectorySeparator + 'cardimg' + DirectorySeparator + '0.jpg';
                ImgExtraMonster[FCurrentCard.Seq].Bitmap.LoadFromFile(AImgPath);
                ImgExtraMonster[FCurrentCard.Seq].RotateAngle:= 270;
                FCurrentCard.Position:= cpDownDef;
              end;
            end;
            Exit;
          end;
          case cmbCardPosition.ItemIndex of
          0:  // up atk
            begin
              AImgPath:= ExtractFilePath(ParamStr(0)) + DirectorySeparator + 'cardimg' + DirectorySeparator + Format('%d.jpg', [FCurrentCard.CardId]);
              ImgMonster[1][FCurrentCard.Seq].Bitmap.LoadFromFile(AImgPath);
              ImgMonster[1][FCurrentCard.Seq].RotateAngle:= 180;
              FCurrentCard.Position:= cpUpAtk;
            end;
          1:  // up def
            begin
              AImgPath:= ExtractFilePath(ParamStr(0)) + DirectorySeparator + 'cardimg' + DirectorySeparator + Format('%d.jpg', [FCurrentCard.CardId]);
              ImgMonster[1][FCurrentCard.Seq].Bitmap.LoadFromFile(AImgPath);
              ImgMonster[1][FCurrentCard.Seq].RotateAngle:= 270;
              FCurrentCard.Position:= cpUpDef;
            end;
          2:  // down def
            begin
              AImgPath:= ExtractFilePath(ParamStr(0)) + DirectorySeparator + 'cardimg' + DirectorySeparator + '0.jpg';
              ImgMonster[1][FCurrentCard.Seq].Bitmap.LoadFromFile(AImgPath);
              ImgMonster[1][FCurrentCard.Seq].RotateAngle:= 270;
              FCurrentCard.Position:= cpDownDef;
            end;
          end;
        end;
      clSZone:
        begin
          // skip pendulum monsters
          if FCurrentCard.CardType = ctPendulum then Exit;
          case cmbCardPosition.ItemIndex of
          0:  // up
            begin
              AImgPath:= ExtractFilePath(ParamStr(0)) + DirectorySeparator + 'cardimg' + DirectorySeparator + Format('%d.jpg', [FCurrentCard.CardId]);
              ImgSpell[1][FCurrentCard.Seq].Bitmap.LoadFromFile(AImgPath);
              FCurrentCard.Position:= cpUp;
            end;
          1:  // down
            begin
              AImgPath:= ExtractFilePath(ParamStr(0)) + DirectorySeparator + 'cardimg' + DirectorySeparator + '0.jpg';
              ImgSpell[1][FCurrentCard.Seq].Bitmap.LoadFromFile(AImgPath);
              FCurrentCard.Position:= cpDown;
            end;
          end;
        end;
      end;
    end;
  end;
end;

procedure TFormHome.cmbCardPositionKillFocus(Sender: TObject);
begin
  cmbCardPosition.Popup.IsOpen:= False;
end;

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

procedure TFormHome.cmbCardWorkAsKillFocus(Sender: TObject);
begin
  cmbCardWorkAs.Popup.IsOpen:= False;
end;

procedure TFormHome.edtCardIndicatorChange(Sender: TObject);
begin
  if (FCurrentCard = nil) then Exit;
  FCurrentCard.IndicatorCount:= trunc(edtCardIndicator.Value);
end;

procedure TFormHome.edtCardIndicatorTyping(Sender: TObject);
begin
  if (FCurrentCard = nil) then Exit;
  FCurrentCard.IndicatorCount:= trunc(edtCardIndicator.Value);
end;

end.

