// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:nil -*- 
// vim: ts=8 sw=2 sts=2 expandtab

/*
 * Ceph - scalable distributed file system
 *
 * Copyright (C) 2004-2006 Sage Weil <sage@newdream.net>
 *
 * This is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 2.1, as published by the Free Software 
 * Foundation.  See file COPYING.
 * 
 */

#ifndef CEPH_MMDSCACHEREJOIN_H
#define CEPH_MMDSCACHEREJOIN_H

#include <string_view>
#include "include/types.h"
#include "mds/CInode.h"
#include "mds/CDir.h"
#include "mds/mdstypes.h"
#include "messages/MMDSOp.h"

// sent from replica to auth

class MMDSCacheRejoin final : public MMDSOp {
public:
  static constexpr int OP_WEAK    = 1;  // replica -> auth, i exist, + maybe open files.
  static constexpr int OP_STRONG  = 2;  // replica -> auth, i exist, + open files and lock state.
  static constexpr int OP_ACK     = 3;  // auth -> replica, here is your lock state.
  static const char *get_opname(int op) {
    switch (op) {
    case OP_WEAK: return "weak";
    case OP_STRONG: return "strong";
    case OP_ACK: return "ack";
    default: ceph_abort(); return 0;
    }
  }

  // -- types --
  struct inode_strong { 
    uint32_t nonce = 0;
    int32_t caps_wanted = 0;
    int32_t filelock = 0, nestlock = 0, dftlock = 0;
    inode_strong() {}
    inode_strong(int n, int cw, int dl, int nl, int dftl) :
      nonce(n), caps_wanted(cw),
      filelock(dl), nestlock(nl), dftlock(dftl) { }
    void encode(ceph::buffer::list &bl) const {
      using ceph::encode;
      encode(nonce, bl);
      encode(caps_wanted, bl);
      encode(filelock, bl);
      encode(nestlock, bl);
      encode(dftlock, bl);
    }
    void decode(ceph::buffer::list::const_iterator &bl) {
      using ceph::decode;
      decode(nonce, bl);
      decode(caps_wanted, bl);
      decode(filelock, bl);
      decode(nestlock, bl);
      decode(dftlock, bl);
    }
    void dump(ceph::Formatter *f) const {
      f->dump_int("nonce", nonce);
      f->dump_int("caps_wanted", caps_wanted);
      f->dump_int("filelock", filelock);
      f->dump_int("nestlock", nestlock);
      f->dump_int("dftlock", dftlock);
    }
    static std::list<inode_strong> generate_test_instances() {
      std::list<inode_strong> ls;
      ls.emplace_back();
      ls.push_back(inode_strong(1, 2, 3, 4, 5));
      return ls;
    }
  };
  WRITE_CLASS_ENCODER(inode_strong)

  struct dirfrag_strong {
    uint32_t nonce = 0;
    int8_t  dir_rep = 0;
    dirfrag_strong() {}
    dirfrag_strong(int n, int dr) : nonce(n), dir_rep(dr) {}
    void encode(ceph::buffer::list &bl) const {
      using ceph::encode;
      encode(nonce, bl);
      encode(dir_rep, bl);
    }
    void decode(ceph::buffer::list::const_iterator &bl) {
      using ceph::decode;
      decode(nonce, bl);
      decode(dir_rep, bl);
    }
    static std::list<dirfrag_strong> generate_test_instances() {
      std::list<dirfrag_strong> ls;
      ls.emplace_back();
      ls.push_back(dirfrag_strong(1, 2));
      return ls;
    }
    void dump(ceph::Formatter *f) const {
      f->dump_unsigned("nonce", nonce);
      f->dump_unsigned("dir_rep", dir_rep);
    }
  };
  WRITE_CLASS_ENCODER(dirfrag_strong)

  struct dn_strong {
    snapid_t first;
    std::string alternate_name;
    inodeno_t ino = 0;
    inodeno_t remote_ino = 0;
    unsigned char remote_d_type = 0;
    uint32_t nonce = 0;
    int32_t lock = 0;
    dn_strong() = default;
    dn_strong(snapid_t f, std::string_view altn, inodeno_t pi, inodeno_t ri,  unsigned char rdt, int n, int l) :
      first(f), alternate_name(altn), ino(pi), remote_ino(ri), remote_d_type(rdt), nonce(n), lock(l) {}
    bool is_primary() const { return ino > 0; }
    bool is_remote() const { return remote_ino > 0; }
    bool is_null() const { return ino == 0 && remote_ino == 0; }
    void encode(ceph::buffer::list &bl) const {
      using ceph::encode;
      encode(first, bl);
      encode(ino, bl);
      encode(remote_ino, bl);
      encode(remote_d_type, bl);
      encode(nonce, bl);
      encode(lock, bl);
      encode(alternate_name, bl);
    }
    void decode(ceph::buffer::list::const_iterator &bl) {
      using ceph::decode;
      decode(first, bl);
      decode(ino, bl);
      decode(remote_ino, bl);
      decode(remote_d_type, bl);
      decode(nonce, bl);
      decode(lock, bl);
      decode(alternate_name, bl);
    }
    static std::list<dn_strong> generate_test_instances() {
      std::list<dn_strong> ls;
      ls.emplace_back();
      ls.push_back(dn_strong(1, "alternate_name", 2, 3, 4, 5, 6));
      return ls;
    }
    void dump(ceph::Formatter *f) const {
      f->dump_unsigned("first", first);
      f->dump_string("alternate_name", alternate_name);
      f->dump_unsigned("ino", ino);
      f->dump_unsigned("remote_ino", remote_ino);
      f->dump_unsigned("remote_d_type", remote_d_type);
      f->dump_unsigned("nonce", nonce);
      f->dump_unsigned("lock", lock);
    }
  };
  WRITE_CLASS_ENCODER(dn_strong)

  struct dn_strong_new {
    snapid_t first;
    std::string alternate_name;
    inodeno_t ino = 0;
    inodeno_t remote_ino = 0;
    inodeno_t referent_ino = 0;
    unsigned char remote_d_type = 0;
    uint32_t nonce = 0;
    int32_t lock = 0;
    dn_strong_new() = default;
    dn_strong_new(snapid_t f, std::string_view altn, inodeno_t pi, inodeno_t ri, inodeno_t ref_ino, unsigned char rdt, int n, int l) :
      first(f), alternate_name(altn), ino(pi), remote_ino(ri), referent_ino(ref_ino), remote_d_type(rdt), nonce(n), lock(l) {}
    bool is_primary() const { return ino > 0; }
    bool is_remote() const { return remote_ino > 0 && referent_ino == 0; }
    bool is_referent_remote() const { return remote_ino > 0 && referent_ino > 0; }
    bool is_null() const { return ino == 0 && remote_ino == 0; }
    void encode(ceph::buffer::list &bl) const {
      ENCODE_START(1, 1, bl);
      encode(first, bl);
      encode(ino, bl);
      encode(remote_ino, bl);
      encode(remote_d_type, bl);
      encode(nonce, bl);
      encode(lock, bl);
      encode(alternate_name, bl);
      encode(referent_ino, bl);
      ENCODE_FINISH(bl);
    }
    void decode(ceph::buffer::list::const_iterator &bl) {
      DECODE_START(1, bl);
      decode(first, bl);
      decode(ino, bl);
      decode(remote_ino, bl);
      decode(remote_d_type, bl);
      decode(nonce, bl);
      decode(lock, bl);
      decode(alternate_name, bl);
      decode(referent_ino, bl);
      DECODE_FINISH(bl);
    }
    static std::list<dn_strong_new> generate_test_instances() {
      std::list<dn_strong_new> ls;
      ls.emplace_back();
      ls.push_back(dn_strong_new(1, "alternate_name", 2, 3, 4, 5, 6, 7));
      return ls;
    }
    void dump(ceph::Formatter *f) const {
      f->dump_unsigned("first", first);
      f->dump_string("alternate_name", alternate_name);
      f->dump_unsigned("ino", ino);
      f->dump_unsigned("referent_ino", referent_ino);
      f->dump_unsigned("remote_ino", remote_ino);
      f->dump_unsigned("remote_d_type", remote_d_type);
      f->dump_unsigned("nonce", nonce);
      f->dump_unsigned("lock", lock);
    }
  };
  WRITE_CLASS_ENCODER(dn_strong_new)

  struct dn_weak {
    snapid_t first;
    inodeno_t ino = 0;
    dn_weak() = default;
    dn_weak(snapid_t f, inodeno_t pi) : first(f), ino(pi) {}
    void encode(ceph::buffer::list &bl) const {
      using ceph::encode;
      encode(first, bl);
      encode(ino, bl);
    }
    void decode(ceph::buffer::list::const_iterator &bl) {
      using ceph::decode;
      decode(first, bl);
      decode(ino, bl);
    }
  };
  WRITE_CLASS_ENCODER(dn_weak)

  struct lock_bls {
    ceph::buffer::list file, nest, dft;
    void encode(ceph::buffer::list& bl) const {
      using ceph::encode;
      encode(file, bl);
      encode(nest, bl);
      encode(dft, bl);
    }
    void decode(ceph::buffer::list::const_iterator& bl) {
      using ceph::decode;
      decode(file, bl);
      decode(nest, bl);
      decode(dft, bl);
    }
  };
  WRITE_CLASS_ENCODER(lock_bls)

  // authpins, xlocks
  struct peer_reqid {
    metareqid_t reqid;
    __u32 attempt;
    peer_reqid() : attempt(0) {}
    peer_reqid(const metareqid_t& r, __u32 a)
      : reqid(r), attempt(a) {}
    void encode(ceph::buffer::list& bl) const {
      using ceph::encode;
      encode(reqid, bl);
      encode(attempt, bl);
    }
    void decode(ceph::buffer::list::const_iterator& bl) {
      using ceph::decode;
      decode(reqid, bl);
      decode(attempt, bl);
    }
  };

  std::string_view get_type_name() const override { return "cache_rejoin"; }
  void print(std::ostream& out) const override {
    out << "cache_rejoin " << get_opname(op);
  }

  // -- builders --
  // inodes
  void add_weak_inode(vinodeno_t i) {
    weak_inodes.insert(i);
  }
  void add_strong_inode(vinodeno_t i, int n, int cw, int dl, int nl, int dftl) {
    strong_inodes[i] = inode_strong(n, cw, dl, nl, dftl);
  }
  void add_inode_locks(CInode *in, __u32 nonce, ceph::buffer::list& bl) {
    using ceph::encode;
    encode(in->ino(), inode_locks);
    encode(in->last, inode_locks);
    encode(nonce, inode_locks);
    encode(bl, inode_locks);
  }
  void add_inode_base(CInode *in, uint64_t features) {
    using ceph::encode;
    encode(in->ino(), inode_base);
    encode(in->last, inode_base);
    ceph::buffer::list bl;
    in->_encode_base(bl, features);
    encode(bl, inode_base);
  }
  void add_inode_authpin(vinodeno_t ino, const metareqid_t& ri, __u32 attempt) {
    authpinned_inodes[ino].push_back(peer_reqid(ri, attempt));
  }
  void add_inode_frozen_authpin(vinodeno_t ino, const metareqid_t& ri, __u32 attempt) {
    frozen_authpin_inodes[ino] = peer_reqid(ri, attempt);
  }
  void add_inode_xlock(vinodeno_t ino, int lt, const metareqid_t& ri, __u32 attempt) {
    xlocked_inodes[ino][lt] = peer_reqid(ri, attempt);
  }
  void add_inode_wrlock(vinodeno_t ino, int lt, const metareqid_t& ri, __u32 attempt) {
    wrlocked_inodes[ino][lt].push_back(peer_reqid(ri, attempt));
  }

  void add_scatterlock_state(CInode *in) {
    if (inode_scatterlocks.count(in->ino()))
      return;  // already added this one
    in->encode_lock_state(CEPH_LOCK_IFILE, inode_scatterlocks[in->ino()].file);
    in->encode_lock_state(CEPH_LOCK_INEST, inode_scatterlocks[in->ino()].nest);
    in->encode_lock_state(CEPH_LOCK_IDFT, inode_scatterlocks[in->ino()].dft);
  }

  // dirfrags
  void add_strong_dirfrag(dirfrag_t df, int n, int dr) {
    strong_dirfrags[df] = dirfrag_strong(n, dr);
  }
  void add_dirfrag_base(CDir *dir) {
    ceph::buffer::list& bl = dirfrag_bases[dir->dirfrag()];
    dir->_encode_base(bl);
  }

  // dentries
  void add_weak_dirfrag(dirfrag_t df) {
    weak_dirfrags.insert(df);
  }
  void add_weak_dentry(inodeno_t dirino, std::string_view dname, snapid_t last, dn_weak& dnw) {
    weak[dirino][string_snap_t(dname, last)] = dnw;
  }
  void add_weak_primary_dentry(inodeno_t dirino, std::string_view dname, snapid_t first, snapid_t last, inodeno_t ino) {
    weak[dirino][string_snap_t(dname, last)] = dn_weak(first, ino);
  }
  void add_strong_dentry(dirfrag_t df, std::string_view dname, std::string_view altn, snapid_t first, snapid_t last, inodeno_t pi, inodeno_t ri, inodeno_t ref_ino, unsigned char rdt, int n, int ls) {
    auto& m = strong_dentries[df];
    m.insert_or_assign(string_snap_t(dname, last), dn_strong(first, altn, pi, ri, rdt, n, ls));
    auto& m1 = strong_dentries_new[df];
    m1.insert_or_assign(string_snap_t(dname, last), dn_strong_new(first, altn, pi, ri, ref_ino, rdt, n, ls));
  }
  void add_dentry_authpin(dirfrag_t df, std::string_view dname, snapid_t last,
			  const metareqid_t& ri, __u32 attempt) {
    authpinned_dentries[df][string_snap_t(dname, last)].push_back(peer_reqid(ri, attempt));
  }
  void add_dentry_xlock(dirfrag_t df, std::string_view dname, snapid_t last,
			const metareqid_t& ri, __u32 attempt) {
    xlocked_dentries[df][string_snap_t(dname, last)] = peer_reqid(ri, attempt);
  }

  // -- encoding --
  void encode_payload(uint64_t features) override {
    using ceph::encode;
    encode(op, payload);
    encode(strong_inodes, payload);
    encode(inode_base, payload);
    encode(inode_locks, payload);
    encode(inode_scatterlocks, payload);
    encode(authpinned_inodes, payload);
    encode(frozen_authpin_inodes, payload);
    encode(xlocked_inodes, payload);
    encode(wrlocked_inodes, payload);
    encode(cap_exports, payload);
    encode(client_map, payload, features);
    encode(imported_caps, payload);
    encode(strong_dirfrags, payload);
    encode(dirfrag_bases, payload);
    encode(weak, payload);
    encode(weak_dirfrags, payload);
    encode(weak_inodes, payload);
    encode(strong_dentries, payload);
    encode(authpinned_dentries, payload);
    encode(xlocked_dentries, payload);
    encode(client_metadata_map, payload);
    encode(strong_dentries_new, payload);
  }
  void decode_payload() override {
    auto p = payload.cbegin();
    using ceph::decode;
    decode(op, p);
    decode(strong_inodes, p);
    decode(inode_base, p);
    decode(inode_locks, p);
    decode(inode_scatterlocks, p);
    decode(authpinned_inodes, p);
    decode(frozen_authpin_inodes, p);
    decode(xlocked_inodes, p);
    decode(wrlocked_inodes, p);
    decode(cap_exports, p);
    decode(client_map, p);
    decode(imported_caps, p);
    decode(strong_dirfrags, p);
    decode(dirfrag_bases, p);
    decode(weak, p);
    decode(weak_dirfrags, p);
    decode(weak_inodes, p);
    decode(strong_dentries, p);
    decode(authpinned_dentries, p);
    decode(xlocked_dentries, p);
    if (header.version >= 2)
      decode(client_metadata_map, p);
    if (header.version >= 3 )
      decode(strong_dentries_new, p);
  }

  // -- data --
  int32_t op = 0;

  // weak
  std::map<inodeno_t, std::map<string_snap_t, dn_weak> > weak;
  std::set<dirfrag_t> weak_dirfrags;
  std::set<vinodeno_t> weak_inodes;
  std::map<inodeno_t, lock_bls> inode_scatterlocks;

  // strong
  std::map<dirfrag_t, dirfrag_strong> strong_dirfrags;
  std::map<dirfrag_t, std::map<string_snap_t, dn_strong> > strong_dentries;
  std::map<dirfrag_t, std::map<string_snap_t, dn_strong_new> > strong_dentries_new;
  std::map<vinodeno_t, inode_strong> strong_inodes;

  // open
  std::map<inodeno_t,std::map<client_t, cap_reconnect_t> > cap_exports;
  std::map<client_t, entity_inst_t> client_map;
  std::map<client_t,client_metadata_t> client_metadata_map;
  ceph::buffer::list imported_caps;

  // full
  ceph::buffer::list inode_base;
  ceph::buffer::list inode_locks;
  std::map<dirfrag_t, ceph::buffer::list> dirfrag_bases;

  std::map<vinodeno_t, std::list<peer_reqid> > authpinned_inodes;
  std::map<vinodeno_t, peer_reqid> frozen_authpin_inodes;
  std::map<vinodeno_t, std::map<__s32, peer_reqid> > xlocked_inodes;
  std::map<vinodeno_t, std::map<__s32, std::list<peer_reqid> > > wrlocked_inodes;
  std::map<dirfrag_t, std::map<string_snap_t, std::list<peer_reqid> > > authpinned_dentries;
  std::map<dirfrag_t, std::map<string_snap_t, peer_reqid> > xlocked_dentries;

private:
  template<class T, typename... Args>
  friend boost::intrusive_ptr<T> ceph::make_message(Args&&... args);
  template<class T, typename... Args>
  friend MURef<T> crimson::make_message(Args&&... args);

  static constexpr int HEAD_VERSION = 3;
  static constexpr int COMPAT_VERSION = 1;

  MMDSCacheRejoin(int o) : MMDSCacheRejoin() { op = o; }
  MMDSCacheRejoin() : MMDSOp{MSG_MDS_CACHEREJOIN, HEAD_VERSION, COMPAT_VERSION} {}
  ~MMDSCacheRejoin() final {}
};

WRITE_CLASS_ENCODER(MMDSCacheRejoin::inode_strong)
WRITE_CLASS_ENCODER(MMDSCacheRejoin::dirfrag_strong)
WRITE_CLASS_ENCODER(MMDSCacheRejoin::dn_strong)
WRITE_CLASS_ENCODER(MMDSCacheRejoin::dn_weak)
WRITE_CLASS_ENCODER(MMDSCacheRejoin::lock_bls)
WRITE_CLASS_ENCODER(MMDSCacheRejoin::peer_reqid)
WRITE_CLASS_ENCODER(MMDSCacheRejoin::dn_strong_new)

inline std::ostream& operator<<(std::ostream& out, const MMDSCacheRejoin::peer_reqid& r) {
  return out << r.reqid << '.' << r.attempt;
}

#endif
