/**********************************************************************************************
   Copyright (C) 2015 Ivo Kronenberg <>

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.

**********************************************************************************************/

#include "device/CDeviceWatcherMac.h"

#include <QtWidgets>

#include "device/IDevice.h"
#include "gis/CGisListWks.h"

CDeviceWatcherMac::CDeviceWatcherMac(CGisListWks* parent) : IDeviceWatcher(parent), worker() {
  connect(&worker, &CDeviceWorker::sigDeviceAdded, this, &CDeviceWatcherMac::slotDeviceAdded);
  connect(&worker, &CDeviceWorker::sigDeviceRemoved, this, &CDeviceWatcherMac::slotDeviceRemoved);
  connect(qApp, &QApplication::aboutToQuit, this, &CDeviceWatcherMac::slotEndListing);
}

void CDeviceWatcherMac::addDevice(const QStorageInfo& storage) {
  if (storage.isValid() && storage.isReady()) {
    QString dev = QString(storage.device()).section('/', -1);
    if (!deviceList.contains(dev)) {
      // We don't care about content of device. This is done in the probeForDevice() method.
      deviceList.append(dev);
      QString path = storage.rootPath();
      QString label = storage.name();

      qDebug() << "label:" << label << " root path: " << path << " device: " << dev;
      probeForDevice(path, dev, label);
    }
  }
}

void CDeviceWatcherMac::slotEndListing() { worker.stop(); }

// Aufruf 1s. nach instanzierung
void CDeviceWatcherMac::slotUpdate() {
  qDebug() << "slotUpdate";
  for (const QStorageInfo& storage : QStorageInfo::mountedVolumes()) {
    addDevice(storage);
  }
}

void CDeviceWatcherMac::slotDeviceAdded(QString dev) {
  // get mount point
  QStorageInfo storageInfo;
  for (const QStorageInfo& storage : QStorageInfo::mountedVolumes()) {
    QString diskName = QString(storage.device()).section('/', -1);
    if (dev == diskName) {
      storageInfo = storage;
    }
  }

  qDebug() << "slotDeviceAdded" << dev << " " << storageInfo.rootPath();

  addDevice(storageInfo);
}

void CDeviceWatcherMac::slotDeviceRemoved(QString dev) {
  qDebug() << "slotDeviceRemoved" << dev;

  deviceList.removeAll(dev);
  listWks->removeDevice(dev);
}

static void onDiskAppear(DADiskRef disk, CFArrayRef keys, void* context) {
  CDeviceWorker* p = static_cast<CDeviceWorker*>(context);
  QString diskName = DADiskGetBSDName(disk);
  qDebug() << "onDiskAppear" << diskName;

  emit p->sigDeviceAdded(diskName);
}

static void onDiskDisappear(DADiskRef disk, void* context) {
  CDeviceWorker* p = static_cast<CDeviceWorker*>(context);
  QString diskName = DADiskGetBSDName(disk);
  qDebug() << "onDiskDisappear" << diskName;

  emit p->sigDeviceRemoved(diskName);
}

CDeviceWorker::CDeviceWorker() : QThread() {
  mSession = DASessionCreate(kCFAllocatorDefault);
  DARegisterDiskDescriptionChangedCallback(mSession, nullptr, kDADiskDescriptionWatchVolumePath, onDiskAppear, this);
  DARegisterDiskDisappearedCallback(mSession, nullptr, onDiskDisappear, this);

  QThread::start();
}

void CDeviceWorker::stop() {
  mStop = true;
  wait();

  DAUnregisterCallback(mSession, (void*)onDiskAppear, this);
  DAUnregisterCallback(mSession, (void*)onDiskDisappear, this);

  qDebug() << "CDeviceWorker Thread.stop";
}

void CDeviceWorker::run() {
  qDebug() << "CDeviceWorker Thread.run";
  mStop = false;

  DASessionScheduleWithRunLoop(mSession, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
  SInt32 result;
  do {
    result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1, true);
  } while (!mStop && result);

  DASessionUnscheduleFromRunLoop(mSession, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
}
