#ifndef BUFFER_H
#define BUFFER_H

#include <QObject>
#include <QSharedPointer>

#include <functional>

#include <global.h>

class QWidget;
class QTimer;

namespace vnotex {
class Node;
class Buffer;
class ViewWindow;
struct FileOpenParameters;
class BufferProvider;
class File;

struct BufferParameters {
  QSharedPointer<BufferProvider> m_provider;
};

class Buffer : public QObject {
  Q_OBJECT
public:
  enum class ProviderType { Internal, External };

  enum class OperationCode { Success, FileMissingOnDisk, FileChangedOutside, Failed };

  enum StateFlag { Normal = 0, FileMissingOnDisk = 0x1, FileChangedOutside = 0x2, Discarded = 0x4 };
  Q_DECLARE_FLAGS(StateFlags, StateFlag);

  Buffer(const BufferParameters &p_parameters, QObject *p_parent = nullptr);

  virtual ~Buffer();

  int getAttachViewWindowCount() const;

  void attachViewWindow(ViewWindow *p_win);
  void detachViewWindow(ViewWindow *p_win);

  // Create a view window to show the content of this buffer.
  // Attach the created view window to this buffer.
  ViewWindow *createViewWindow(const QSharedPointer<FileOpenParameters> &p_paras,
                               QWidget *p_parent);

  // Whether this buffer matches @p_node.
  bool match(const Node *p_node) const;

  // Whether this buffer matches @p_filePath.
  bool match(const QString &p_filePath) const;

  // Buffer name.
  QString getName() const;

  QString getPath() const;

  // In some cases, getPath() may point to a container containting all the stuffs.
  // getContentPath() will return the real path to the file providing the content.
  QString getContentPath() const;

  // Get the base path to resolve resources.
  QString getResourcePath() const;

  // Return nullptr if not available.
  QSharedPointer<File> getFile() const;

  ID getId() const;

  // Get buffer content.
  // It may differ from the content on disk.
  // For performance, we need to sync the content with ViewWindow before returning
  // the latest content.
  const QString &getContent() const;

  // @p_revision will be set before contentsChanged is emitted.
  void setContent(const QString &p_content, int &p_revision);

  // Invalidate the content of buffer.
  // Need to sync with @p_win to get the latest content.
  // @p_setRevision will be called to set revision before contentsChanged is emitted.
  void invalidateContent(const ViewWindow *p_win, const std::function<void(int)> &p_setRevision);

  // Sync content with @p_win if @p_win is the window needed to sync.
  void syncContent(const ViewWindow *p_win);

  int getRevision() const;

  bool isModified() const;
  void setModified(bool p_modified);

  bool isReadOnly() const;

  // Save buffer content to file.
  OperationCode save(bool p_force);

  // Discard changes and reload file.
  OperationCode reload();

  // Discard the buffer which will invalidate the buffer.
  void discard();

  // Buffer is about to be deleted.
  void close();

  // Insert image from @p_srcImagePath.
  // Return inserted image file path.
  virtual QString insertImage(const QString &p_srcImagePath, const QString &p_imageFileName);

  virtual QString insertImage(const QImage &p_image, const QString &p_imageFileName);

  virtual void removeImage(const QString &p_imagePath);

  const QString &getBackupFileOfPreviousSession() const;

  void discardBackupFileOfPreviousSession();

  void recoverFromBackupFileOfPreviousSession();

  // Whether this buffer's provider is a child of @p_node or an attachment of @p_node.
  bool isChildOf(const Node *p_node) const;

  Node *getNode() const;

  bool isAttachmentSupported() const;

  bool hasAttachment() const;

  QString getAttachmentFolderPath() const;

  // @p_destFolderPath: folder path locating in attachment folder. Use the root folder if empty.
  QStringList addAttachment(const QString &p_destFolderPath, const QStringList &p_files);

  QString newAttachmentFile(const QString &p_destFolderPath, const QString &p_name);

  QString newAttachmentFolder(const QString &p_destFolderPath, const QString &p_name);

  QString renameAttachment(const QString &p_path, const QString &p_name);

  void removeAttachment(const QStringList &p_paths);

  // Judge whether file @p_path is attachment.
  bool isAttachment(const QString &p_path) const;

  bool isTagSupported() const;

  ProviderType getProviderType() const;

  bool checkFileExistsOnDisk();

  bool checkFileChangedOutside();

  StateFlags state() const;

  static QString readBackupFile(const QString &p_filePath);

signals:
  void attachedViewWindowEmpty();

  void modified(bool p_modified);

  void contentsChanged();

  void nameChanged();

  void attachmentChanged();

  // This buffer is AutoSavePolicy::AutoSave.
  void autoSaved();

protected:
  virtual ViewWindow *createViewWindowInternal(const QSharedPointer<FileOpenParameters> &p_paras,
                                               QWidget *p_parent) = 0;

  QSharedPointer<BufferProvider> m_provider;

private slots:
  void autoSave();

private:
  void syncContent();

  void readContent();

  // Get the path of the image folder.
  QString getImageFolderPath() const;

  void writeBackupFile();

  // Generate backup file head.
  QString generateBackupFileHead() const;

  void checkBackupFileOfPreviousSession();

  bool isBackupFileOfBuffer(const QString &p_file) const;

  // Will be assigned uniquely once created.
  const ID m_id = 0;

  // Revision of contents.
  int m_revision = 0;

  // If the buffer is modified, m_content reflect the latest changes instead
  // of the file content.
  QString m_content;

  bool m_readOnly = false;

  bool m_modified = false;

  int m_attachedViewWindowCount = 0;

  const ViewWindow *m_viewWindowToSync = nullptr;

  // Managed by QObject.
  QTimer *m_autoSaveTimer = nullptr;

  QString m_backupFilePath;

  QString m_backupFilePathOfPreviousSession;

  StateFlags m_state = StateFlag::Normal;
};
} // namespace vnotex

Q_DECLARE_OPERATORS_FOR_FLAGS(vnotex::Buffer::StateFlags)

#endif // BUFFER_H
