// SPDX-FileCopyrightText: Copyright (c) Kitware Inc.
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkClientServerStream.h"
#include "vtkDoubleArray.h"
#include "vtkNew.h"
#include "vtkStringArray.h"
#include "vtkVariantArray.h"

#include <iostream>

static double dblIni[] = { 904., 906., 917. };
static const char* strIni[] = { "901", "Turbo", "Targa" };

template <class T>
struct Help
{
  static void Store(vtkClientServerStream& css)
  {
    T v = 123;
    T a[2] = { 12, 3 };
    css << v << vtkClientServerStream::InsertArray(a, 2);
  }
  static bool Check(vtkClientServerStream& css, int& arg)
  {
    if (css.GetArgument(0, arg).Size != sizeof(vtkTypeUInt32) + sizeof(T))
    {
      return false;
    }

    T v;
    if (!css.GetArgument(0, arg++, &v) || v != 123)
    {
      return false;
    }

    if (css.GetArgument(0, arg).Size != 2 * (sizeof(vtkTypeUInt32) + sizeof(T)))
    {
      return false;
    }

    T a[2];
    if (!css.GetArgument(0, arg++, a, 2) || a[0] != 12 || a[1] != 3)
    {
      return false;
    }
    return true;
  }
};

// Store a bunch of values in a stream.
static void do_store(vtkClientServerStream& css)
{
  vtkClientServerID id(123);
  css.Reserve(64);
  css.Reset();
  css << vtkClientServerStream::Reply;
  css << true;
  css << id;
  Help<char>::Store(css);
  Help<short>::Store(css);
  Help<int>::Store(css);
  Help<long>::Store(css);
  Help<signed char>::Store(css);
  Help<unsigned char>::Store(css);
  Help<unsigned short>::Store(css);
  Help<unsigned int>::Store(css);
  Help<unsigned long>::Store(css);
  Help<long long>::Store(css);
  Help<unsigned long long>::Store(css);
  Help<float>::Store(css);
  Help<double>::Store(css);

  vtkVariant varStr("911");
  css << varStr;
  vtkVariant varNum(356.0);
  css << varNum;

  // Test a variant that is itself an array of primitives (doubles).
  vtkNew<vtkDoubleArray> dblArr;
  dblArr->SetArray(dblIni, sizeof(dblIni) / sizeof(dblIni[0]), 1);
  vtkVariant varDblArr(dblArr.GetPointer());
  css << varDblArr;

  // Test a variant that is itself an array of composites (strings).
  vtkNew<vtkStringArray> strArr;
  strArr->SetNumberOfValues(sizeof(strIni) / sizeof(strIni[0]));
  for (vtkIdType i = 0; i < strArr->GetNumberOfValues(); ++i)
  {
    strArr->SetValue(i, strIni[i]);
  }
  vtkVariant varStrArr(strArr.GetPointer());
  css << varStrArr;

  // Test a variant that is itself an array of composites (variants).
  vtkNew<vtkVariantArray> varArr;
  varArr->SetNumberOfValues(sizeof(strIni) / sizeof(strIni[0]));
  for (vtkIdType i = 0; i < varArr->GetNumberOfValues(); ++i)
  {
    vtkVariant tmp(strIni[i]);
    bool numeric;
    double x = tmp.ToDouble(&numeric);
    if (numeric)
    {
      varArr->SetVariantValue(i, x);
    }
    else
    {
      varArr->SetVariantValue(i, strIni[i]);
    }
  }
  vtkVariant varVarArr(varArr.GetPointer());
  css << varVarArr;

  vtkVariant varInvalid;
  css << varInvalid;

  css << "123";
  {
    vtkClientServerStream nested;
    nested << vtkClientServerStream::Reply << "456" << vtkClientServerStream::End;
    css << nested;
  }
  css << vtkClientServerStream::End;
}

// Check stored values in a stream.
static bool do_check(vtkClientServerStream& css)
{
  if (css.GetNumberOfMessages() != 1)
  {
    return false;
  }
  if (css.GetCommand(0) != vtkClientServerStream::Reply)
  {
    return false;
  }
  int arg = 0;
  {
    if (css.GetArgument(0, arg).Size != sizeof(vtkTypeUInt32) + sizeof(bool))
    {
      return false;
    }
    bool b;
    if (!css.GetArgument(0, arg++, &b) || b != true)
    {
      return false;
    }
  }
  {
    if (css.GetArgument(0, arg).Size != sizeof(vtkTypeUInt32) + sizeof(vtkClientServerID().ID))
    {
      return false;
    }
    vtkClientServerID id;
    if (!css.GetArgument(0, arg++, &id) || id.ID != 123)
    {
      return false;
    }
  }
  {
    if (!Help<char>::Check(css, arg))
    {
      return false;
    }
    if (!Help<short>::Check(css, arg))
    {
      return false;
    }
    if (!Help<int>::Check(css, arg))
    {
      return false;
    }
    if (!Help<long>::Check(css, arg))
    {
      return false;
    }
    if (!Help<signed char>::Check(css, arg))
    {
      return false;
    }
    if (!Help<unsigned char>::Check(css, arg))
    {
      return false;
    }
    if (!Help<unsigned short>::Check(css, arg))
    {
      return false;
    }
    if (!Help<unsigned int>::Check(css, arg))
    {
      return false;
    }
    if (!Help<unsigned long>::Check(css, arg))
    {
      return false;
    }
    if (!Help<long long>::Check(css, arg))
    {
      return false;
    }
    if (!Help<unsigned long long>::Check(css, arg))
    {
      return false;
    }
    if (!Help<float>::Check(css, arg))
    {
      return false;
    }
    if (!Help<double>::Check(css, arg))
    {
      return false;
    }
  }
  {
    vtkVariant varOut;
    if (!css.GetArgument(0, arg, &varOut) || varOut.ToString() != "911")
    {
      std::cout << "Variant was \"" << varOut.ToString().c_str() << "\" instead of 911\n";
      return false;
    }
    if (!css.GetArgument(0, arg, &varOut) || varOut.ToDouble() != 356.0)
    {
      std::cout << "Variant was \"" << varOut.ToString().c_str() << "\" instead of 356.0\n";
      return false;
    }
    vtkDoubleArray* dblArr;
    if (!css.GetArgument(0, arg, &varOut) ||
      !(dblArr = vtkDoubleArray::SafeDownCast(varOut.ToArray())) ||
      dblArr->GetMaxId() != (sizeof(dblIni) / sizeof(dblIni[0]) - 1) ||
      dblArr->GetValue(0) != dblIni[0] || dblArr->GetValue(1) != dblIni[1] ||
      dblArr->GetValue(2) != dblIni[2])
    {
      std::cout << "Variant was \"" << varOut.ToString().c_str()
                << "\" instead of (904.0, 906.0, 917.0)\n";
      return false;
    }
    vtkStringArray* strArr;
    if (!css.GetArgument(0, arg, &varOut) ||
      !(strArr = vtkStringArray::SafeDownCast(varOut.ToArray())) ||
      strArr->GetMaxId() != (sizeof(strIni) / sizeof(strIni[0]) - 1) ||
      strArr->GetValue(0) != strIni[0] || strArr->GetValue(1) != strIni[1] ||
      strArr->GetValue(2) != strIni[2])
    {
      std::cout << "Variant was \"" << varOut.ToString().c_str()
                << "\" instead of (\"901\", \"Turbo\", \"Targa\")\n";
      return false;
    }
    vtkVariantArray* varArr;
    if (!css.GetArgument(0, arg, &varOut) ||
      !(varArr = vtkVariantArray::SafeDownCast(varOut.ToArray())) ||
      varArr->GetMaxId() != (sizeof(strIni) / sizeof(strIni[0]) - 1) ||
      varArr->GetValue(0) != vtkVariant(strIni[0]).ToDouble() || varArr->GetValue(1) != strIni[1] ||
      varArr->GetValue(2) != strIni[2])
    {
      std::cout << "Variant was \"" << varOut.ToString().c_str()
                << "\" instead of (901.0, \"Turbo\", \"Targa\")\n";
      return false;
    }
    if (!css.GetArgument(0, arg, &varOut) || varOut.IsValid())
    {
      std::cout << "Variant that was supposed to be invalid is valid\n";
      return false;
    }
  }
  {
    const char* s;
    if (!css.GetArgument(0, arg++, &s) || strcmp(s, "123") != 0)
    {
      return false;
    }
  }
  {
    const char* s;
    vtkClientServerStream nested;
    if (!css.GetArgument(0, arg++, &nested))
    {
      return false;
    }
    if (!nested.GetArgument(0, 0, &s) || strcmp(s, "456") != 0)
    {
      return false;
    }
  }
  return true;
}

static bool do_test()
{
  // Construct a stream and store values.
  vtkClientServerStream css1;
  do_store(css1);

  // Cover stream print code.
  std::cout << "-----------------------------------------------------------\n";
  css1.Print(std::cout);
  std::cout << "-----------------------------------------------------------\n";

  // Copy the stream in various ways.
  vtkClientServerStream css2(css1);
  vtkClientServerStream css3;
  css3.Copy(&css2);
  vtkClientServerStream css4;
  if (!css4.StreamFromString(css3.StreamToString()))
  {
    std::cerr << "FAILED: StreamToString or StreamFromString failed." << endl;
    return false;
  }
  vtkClientServerStream css5;
  {
    const unsigned char* data;
    size_t length;
    if (!css4.GetData(&data, &length))
    {
      std::cerr << "FAILED: GetData failed." << endl;
      return false;
    }
    if (!css5.SetData(data, length))
    {
      std::cerr << "FAILED: SetData failed." << endl;
      return false;
    }
  }

  if (!do_check(css1))
  {
    std::cerr << "FAILED: Stream contents could not correctly be retrieved." << endl;
    return false;
  }
  if (!do_check(css2))
  {
    std::cerr << "FAILED: Copy constructor did not copy stream properly." << endl;
    return false;
  }
  if (!do_check(css3))
  {
    std::cerr << "FAILED: Copy method did not copy stream properly." << endl;
    return false;
  }
  if (!do_check(css4))
  {
    std::cerr << "FAILED: String(To/From)Stream did not copy stream properly." << endl;
    return false;
  }
  if (!do_check(css5))
  {
    std::cerr << "FAILED: (Get/Set)Data did not copy stream properly." << endl;
    return false;
  }
  return true;
}

extern int coverClientServer(int, char*[])
{
  return do_test() ? 0 : 1;
}
