/*
 * Deskflow -- mouse and keyboard sharing utility
 * Copyright (C) 2012-2016 Symless Ltd.
 * Copyright (C) 2004 Chris Schoeneman
 *
 * This package is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * found in the file LICENSE that should have accompanied this file.
 *
 * This package 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 "deskflow/IClipboard.h"
#include "common/stdvector.h"

//
// IClipboard
//

void IClipboard::unmarshall(
    IClipboard *clipboard, const String &data, Time time) {
  assert(clipboard != NULL);

  const char *index = data.data();

  if (clipboard->open(time)) {
    // clear existing data
    clipboard->empty();

    // read the number of formats
    const UInt32 numFormats = readUInt32(index);
    index += 4;

    // read each format
    for (UInt32 i = 0; i < numFormats; ++i) {
      // get the format id
      IClipboard::EFormat format =
          static_cast<IClipboard::EFormat>(readUInt32(index));
      index += 4;

      // get the size of the format data
      UInt32 size = readUInt32(index);
      index += 4;

      // save the data if it's a known format.  if either the client
      // or server supports more clipboard formats than the other
      // then one of them will get a format >= kNumFormats here.
      if (format < IClipboard::kNumFormats) {
        clipboard->add(format, String(index, size));
      }
      index += size;
    }

    // done
    clipboard->close();
  }
}

String IClipboard::marshall(const IClipboard *clipboard) {
  // return data format:
  // 4 bytes => number of formats included
  // 4 bytes => format enum
  // 4 bytes => clipboard data size n
  // n bytes => clipboard data
  // back to the second 4 bytes if there is another format

  assert(clipboard != NULL);

  String data;

  std::vector<String> formatData;
  formatData.resize(IClipboard::kNumFormats);
  // FIXME -- use current time
  if (clipboard->open(0)) {

    // compute size of marshalled data
    UInt32 size = 4;
    UInt32 numFormats = 0;
    for (UInt32 format = 0; format != IClipboard::kNumFormats; ++format) {
      if (clipboard->has(static_cast<IClipboard::EFormat>(format))) {
        ++numFormats;
        formatData[format] =
            clipboard->get(static_cast<IClipboard::EFormat>(format));
        size += 4 + 4 + (UInt32)formatData[format].size();
      }
    }

    // allocate space
    data.reserve(size);

    // marshall the data
    writeUInt32(&data, numFormats);
    for (UInt32 format = 0; format != IClipboard::kNumFormats; ++format) {
      if (clipboard->has(static_cast<IClipboard::EFormat>(format))) {
        writeUInt32(&data, format);
        writeUInt32(&data, (UInt32)formatData[format].size());
        data += formatData[format];
      }
    }
    clipboard->close();
  }

  return data;
}

bool IClipboard::copy(IClipboard *dst, const IClipboard *src) {
  assert(dst != NULL);
  assert(src != NULL);

  return copy(dst, src, src->getTime());
}

bool IClipboard::copy(IClipboard *dst, const IClipboard *src, Time time) {
  assert(dst != NULL);
  assert(src != NULL);

  bool success = false;
  if (src->open(time)) {
    if (dst->open(time)) {
      if (dst->empty()) {
        for (SInt32 format = 0; format != IClipboard::kNumFormats; ++format) {
          IClipboard::EFormat eFormat = (IClipboard::EFormat)format;
          if (src->has(eFormat)) {
            dst->add(eFormat, src->get(eFormat));
          }
        }
        success = true;
      }
      dst->close();
    }
    src->close();
  }

  return success;
}

UInt32 IClipboard::readUInt32(const char *buf) {
  const unsigned char *ubuf = reinterpret_cast<const unsigned char *>(buf);
  return (static_cast<UInt32>(ubuf[0]) << 24) |
         (static_cast<UInt32>(ubuf[1]) << 16) |
         (static_cast<UInt32>(ubuf[2]) << 8) | static_cast<UInt32>(ubuf[3]);
}

void IClipboard::writeUInt32(String *buf, UInt32 v) {
  *buf += static_cast<UInt8>((v >> 24) & 0xff);
  *buf += static_cast<UInt8>((v >> 16) & 0xff);
  *buf += static_cast<UInt8>((v >> 8) & 0xff);
  *buf += static_cast<UInt8>(v & 0xff);
}
