/*****************************************************************************\
*                                                                             *
*  Name   : test_com_array                                                    *
*  Author : Chris Koeritz                                                     *
*                                                                             *
*  Purpose:                                                                   *
*                                                                             *
*    Exercises the com_array class to make sure no obvious bugs exist.        *
*                                                                             *
*******************************************************************************
* Copyright (c) 1998-$now By Author.  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 2 of   *
* the License or (at your option) any later version.  This is online at:      *
*     http://www.fsf.org/copyleft/gpl.html                                    *
* Please send any updates to: fred@gruntose.com                               *
\*****************************************************************************/

#include <basis/array.h>
#include <basis/common_templates.h>
#include <basis/convert_utf.h>
#include <basis/function.h>
#include <basis/guards.h>
#include <basis/istring.h>
#include <com_ext/com_array.h>
#include <opsystem/startup_code.h>
#include <textual/string_convert.h>

#include <iostream>

BASIS_STARTUP_CODE;

using namespace std;

using namespace com_extensions;

int WINAPI WinMain(application_instance instance,
    application_instance prev_instance, LPSTR lpCmdLine, int nCmdShow)
{
  CoInitialize(NIL);

  {
    // testing com_array of bytes.
    com_array byter;
    if (byter.initialized())
      deadly_error("byte test", __WHERE__.s(), "already initialized");
    array<dim_bound> bounds;
    bounds += dim_bound(10, -4);
    bounds += dim_bound(8, 0);
    bounds += dim_bound(14, -13);
    byter.reset(BYTES, 3, bounds);
    if (!byter.initialized())
      deadly_error("byte test", __WHERE__.s(), "not initialized after init");

    int i, j, k;  // ms bug neutral (since they don't really do ansi).

    // fill the array with known values.
    for (i = -4; i <= 5; i++)
      for (j = 0; j <= 7; j++)
        for (k = -13; k <= 0; k++) {
          int_array where;
          where += i; where += j; where += k;
          if (!byter.put(where, _variant_t(byte((i * j * k) % 256))))
            deadly_error("byte test", __WHERE__.s(), "failed to put");
        }

    // now test what the array should have.
    for (i = -4; i <= 5; i++) {
      for (j = 0; j <= 7; j++) {
        for (k = -13; k <= 0; k++) {
          int_array where;
          where += i; where += j; where += k;
          byte expected = byte((i * j * k) % 256);
          _variant_t got(50L);
          byter.get(where, got);
          if (byte(got) != expected)
            deadly_error("byte test", __WHERE__.s(), "wrong value in array");
          cout << istring(istring::SPRINTF, "%02x ", int(expected)).s();
        }
        cout << endl;  // end of line of k.
      }
      cout << endl << "-------------" << endl << endl;
    }

    // now test the accessors that work with the SAFEARRAY directly.
    SAFEARRAY *low = byter.access();
    byte *data;
    SafeArrayAccessData(low, (void **)&data);  // automatically locks.
    if (byter.element_size() != sizeof(byte))
      deadly_error("byte test", __WHERE__.s(), "wrong element size");

    // this loops through the raw data using a computed index.
    for (i = -4; i <= 5; i++)
      for (j = 0; j <= 7; j++)
        for (k = -13; k <= 0; k++) {
          // the index is computed by zero-biasing the indices and multiplying
          // by the number of elements in each stage.  this is the only one
          // of the tests that spells out how this is computed; the others use
          // the com_array index function.
          int index = (i + 4) + (j - 0) * 10 + (k + 13) * 10 * 8;
            // the first index is least significant?  and so the later indices
            // get multiplied by all of the previous ranges.
          byte expected = byte((i * j * k) % 256);
          byte actual = data[index];
          if (actual != expected)
            deadly_error("byte test", __WHERE__.s(), "wrong value in array");
        }
    SafeArrayUnaccessData(low);  // automatically unlocks.
  }

  {
    // testing com_array of BSTRs.
    com_array beaster;
    if (beaster.initialized())
      deadly_error("BSTR test", __WHERE__.s(), "already initialized");
    array<dim_bound> bounds;
    bounds += dim_bound(10, -4);
    bounds += dim_bound(8, 0);
    bounds += dim_bound(14, -13);
    beaster.reset(STRINGS, 3, bounds);
    if (!beaster.initialized())
      deadly_error("BSTR test", __WHERE__.s(), "not initialized after init");

    // some known strings to store.
    array<istring> fodder;
    fodder += "blort";
    fodder += "nervid";
    fodder += "binkus";
    fodder += "alipua";
    fodder += "wanger";

    int i, j, k;  // ms bug neutral (since they don't really do ansi).

    // fill the array with known values.
    for (i = -4; i <= 5; i++)
      for (j = 0; j <= 7; j++)
        for (k = -13; k <= 0; k++) {
          int_array where;
          where += i; where += j; where += k;
          int which = absolute_value(((i * 10 * 8) + (j * 8) + k) % 5);
          if ( (which < 0) || (which > 4) )
            deadly_error("BSTR test", __WHERE__.s(), "index out of range");
          _bstr_t to_store = string_convert::to_bstr_t(fodder[which]);
          if (!fodder[which].length())
            deadly_error("BSTR test", __WHERE__.s(), "string has no length");
          if ((istring)(char *)to_store != fodder[which])
            deadly_error("BSTR test", __WHERE__.s(), "string doesn't match");
          if (!beaster.put(where, _variant_t(to_store)))
            deadly_error("BSTR test", __WHERE__.s(), "failed to put");
        }

    // now test what the array should have.
    for (i = -4; i <= 5; i++)
      for (j = 0; j <= 7; j++)
        for (k = -13; k <= 0; k++) {
          int_array where;
          where += i; where += j; where += k;
          int which = absolute_value(((i * 10 * 8) + (j * 8) + k) % 5);
          istring expected = fodder[which];
          _variant_t got;
          beaster.get(where, got);
//          MessageBox(0, (char *)(_bstr_t)got, "found", MB_OK);
          if (istring((char *)(_bstr_t)got) != expected)
            deadly_error("BSTR test", __WHERE__.s(), "wrong value in array");
        }

    // now test the accessors that work with the SAFEARRAY directly.
    SAFEARRAY *low = beaster.access();
    BSTR *data;
    SafeArrayAccessData(low, (void **)&data);  // automatically locks.
    if (beaster.element_size() != sizeof(BSTR))
      deadly_error("BSTR test", __WHERE__.s(), "wrong element size");

    // this loops through the raw data using a computed index.
    for (i = -4; i <= 5; i++)
      for (j = 0; j <= 7; j++)
        for (k = -13; k <= 0; k++) {
          int_array index_list;
          index_list += i; index_list += j; index_list += k;
          int index = beaster.index(index_list);
          int which = absolute_value(((i * 10 * 8) + (j * 8) + k) % 5);
          istring expected = fodder[which];
          _bstr_t actual(data[index], true);
          if (istring((char *)actual) != expected)
            deadly_error("BSTR test", __WHERE__.s(), "wrong value in array");
        }
    SafeArrayUnaccessData(low);  // automatically unlocks.
  }

  {
    // testing com_array of IUnknowns.
    com_array unky;
    if (unky.initialized())
      deadly_error("IUnknown test", __WHERE__.s(), "already initialized");
    array<dim_bound> bounds;
    bounds += dim_bound(10, -4);
    bounds += dim_bound(8, 0);
    bounds += dim_bound(14, -13);
    unky.reset(UNKNOWNS, 3, bounds);
    if (!unky.initialized())
      deadly_error("IUnknown test", __WHERE__.s(), "uninitialized after init");

    int i, j, k;  // ms bug neutral (since they don't really do ansi).

    array<IUnknown *> interfaces;  // our interfaces to store.
    // the macro looks up a class id by name, gets an instance of the class
    // and stuffs the pointer into our array.
    #define GET_UNK(name) { \
      try { \
        if (CLSIDFromProgID(string_convert::to_bstr_t(istring(name)), &tmp) != S_OK) \
          deadly_error("IUnknown test", __WHERE__.s(), \
              #name " class missing"); \
        if ((err = CoCreateInstance(tmp, NIL, CLSCTX_INPROC_SERVER, \
            IID_IUnknown, (void **)&to_store)) != S_OK) \
          deadly_error("IUnknown test", __WHERE__.s(), (istring(#name \
              " instancing failed: ") \
              + portable::system_error_text(err)).s()); \
        interfaces += to_store; \
      } catch (_com_error &err) { \
        char *complaint = "COM Error During GET_UNK on " #name; \
        ::MessageBox(NIL, err.ErrorMessage(), \
            to_unicode_temp(complaint), MB_OK); \
      } catch (...) { \
        char *complaint = "Unexpected Exception During GET_UNK on " #name; \
        ::MessageBox(NIL, to_unicode_temp(complaint), \
            to_unicode_temp("Exception!"), MB_OK); \
      } \
    }

    {
      // populate the array of interfaces.
      CLSID tmp;
      int err;
      IUnknown *to_store;
      GET_UNK("file");
      GET_UNK("clsid");
//add more.
    }

    // fill the array with known values.
    for (i = -4; i <= 5; i++)
      for (j = 0; j <= 7; j++)
        for (k = -13; k <= 0; k++) {
          int_array where;
          where += i; where += j; where += k;
          int which = absolute_value(((i * 10 * 8) + (j * 8) + k)
              % interfaces.length());
          if ( (which < 0) || (which >= interfaces.length()) )
            deadly_error("IUnknown test", __WHERE__.s(), "index out of range");
          IUnknown *to_store = interfaces[which];
          if (!unky.put(where, _variant_t(to_store)))
            deadly_error("IUnknown test", __WHERE__.s(), "failed to put");
        }

    // now test what the array should have.
    for (i = -4; i <= 5; i++)
      for (j = 0; j <= 7; j++)
        for (k = -13; k <= 0; k++) {
          int_array where;
          where += i; where += j; where += k;
          int which = absolute_value(((i * 10 * 8) + (j * 8) + k)
              % interfaces.length());
          IUnknown *expected = interfaces[which];
          _variant_t got;
          unky.get(where, got);
          IUnknown *got2 = (IUnknown *)got;
          if (got2 != expected)
            deadly_error("IUnknown test", __WHERE__.s(), "wrong value found");
          got2->Release();
        }

    // now test the accessors that work with the SAFEARRAY directly.
    SAFEARRAY *low = unky.access();
    IUnknown **data;
    SafeArrayAccessData(low, (void **)&data);  // automatically locks.
    if (unky.element_size() != sizeof(IUnknown *))
      deadly_error("IUnknown test", __WHERE__.s(), "wrong element size");

    // this loops through the raw data using a computed index.
    for (i = -4; i <= 5; i++)
      for (j = 0; j <= 7; j++)
        for (k = -13; k <= 0; k++) {
          int_array index_list;
          index_list += i; index_list += j; index_list += k;
          int index = unky.index(index_list);
          int which = absolute_value(((i * 10 * 8) + (j * 8) + k)
              % interfaces.length());
          IUnknown *expected = interfaces[which];
          IUnknown *actual = data[index];
          if (actual != expected)
            deadly_error("IUnknown test", __WHERE__.s(), "wrong value found");
        }
    SafeArrayUnaccessData(low);  // automatically unlocks.

    // free the interfaces we created.
    for (i = 0; i < interfaces.length(); i++) interfaces[i]->Release();
  }

  {
    // testing com_array of VARIANTs.
    com_array varmint;
    if (varmint.initialized())
      deadly_error("VARIANT test", __WHERE__.s(), "already initialized");
    array<dim_bound> bounds;
    bounds += dim_bound(10, -4);
    bounds += dim_bound(8, 0);
    bounds += dim_bound(14, -13);
    varmint.reset(VARIANTS, 3, bounds);
    if (!varmint.initialized())
      deadly_error("VARIANT test", __WHERE__.s(), "not initialized after init");

    // some known variants to store.
    array<_variant_t> fodder;
    fodder += _variant_t((short)40);
    fodder += _variant_t(692987L);
    fodder += _variant_t("hallefoolyah");
    fodder += _variant_t();
    fodder += _variant_t(31.923);
    fodder += _variant_t((double)31.923);
    {
      // grab the file class's id.
      CLSID tmp;
      int err;
      IUnknown *to_store;
      try {
        if (CLSIDFromProgID(string_convert::to_bstr_t(istring("file")), &tmp) != S_OK)
          deadly_error("IUnknown test", __WHERE__.s(), "file class missing");
        if ((err = CoCreateInstance(tmp, NIL, CLSCTX_INPROC_SERVER,
            IID_IUnknown, (void **)&to_store)) != S_OK)
          deadly_error("IUnknown test", __WHERE__.s(), (istring("file "
              "instancing failed: ") + portable::system_error_text(err)).s());
        fodder += _variant_t(to_store);
        to_store->Release();  // done now with this one.
      } catch (_com_error &e) {
        ::MessageBox(NIL, e.ErrorMessage(), to_unicode_temp("COM Error During "
            "Interface Query on file."), MB_OK);
      } catch (...) {
        ::MessageBox(NIL, to_unicode_temp("Unexpected Exception During "
            "Interface Query on file."), to_unicode_temp("Exception!"), MB_OK);
      }
    }

    int i, j, k;  // ms bug neutral (since they don't really do ansi).

    // fill the array with known values.
    for (i = -4; i <= 5; i++)
      for (j = 0; j <= 7; j++)
        for (k = -13; k <= 0; k++) {
          int_array where;
          where += i; where += j; where += k;
          int which = absolute_value(((i * 10 * 8) + (j * 8) + k)
              % fodder.length());
          if ( (which < 0) || (which >= fodder.length()) )
            deadly_error("VARIANT test", __WHERE__.s(), "index out of range");
          _variant_t to_store = fodder[which];
          if (!varmint.put(where, to_store))
            deadly_error("VARIANT test", __WHERE__.s(), "failed to put");
        }

    // now test what the array should have.
    for (i = -4; i <= 5; i++)
      for (j = 0; j <= 7; j++)
        for (k = -13; k <= 0; k++) {
          int_array where;
          where += i; where += j; where += k;
          int which = absolute_value(((i * 10 * 8) + (j * 8) + k)
              % fodder.length());
          _variant_t &expected = fodder[which];
          _variant_t got;
          varmint.get(where, got);
          if (got != expected)
            deadly_error("VARIANT test", __WHERE__.s(), "wrong value in array");
        }

    // now test the accessors that work with the SAFEARRAY directly.
    SAFEARRAY *low = varmint.access();
    VARIANT *data;
    SafeArrayAccessData(low, (void **)&data);  // automatically locks.
    if (varmint.element_size() != sizeof(VARIANT))
      deadly_error("VARIANT test", __WHERE__.s(), "wrong element size");

    // this loops through the raw data using a computed index.
    for (i = -4; i <= 5; i++)
      for (j = 0; j <= 7; j++)
        for (k = -13; k <= 0; k++) {
          int_array index_list;
          index_list += i; index_list += j; index_list += k;
          int index = varmint.index(index_list);
          int which = absolute_value(((i * 10 * 8) + (j * 8) + k)
              % fodder.length());
          _variant_t expected = fodder[which];
          VARIANT actual = data[index];
          if (_variant_t(actual) != expected)
            deadly_error("VARIANT test", __WHERE__.s(), "wrong value in array");
        }
    SafeArrayUnaccessData(low);  // automatically unlocks.
  }

  {
    // testing conversion between com_array and _variant_t.
    com_array petard;
    if (petard.initialized())
      deadly_error("conversion test", __WHERE__.s(), "already initialized");
    array<dim_bound> bounds;
    bounds += dim_bound(10, -4);
    bounds += dim_bound(8, 0);
    bounds += dim_bound(14, -13);
    petard.reset(VARIANTS, 3, bounds);
    if (!petard.initialized())
      deadly_error("conversion test", __WHERE__.s(),
          "not initialized after init");

    // some known variants to store.
    array<_variant_t> fodder;
    fodder += _variant_t((short)40);
    fodder += _variant_t(692987L);
    fodder += _variant_t("hallefoolyah");
    fodder += _variant_t();
    fodder += _variant_t(31.923);
    fodder += _variant_t((double)31.923);

    int i, j, k;  // ms bug neutral (since they don't really do ansi).

    // fill the array with known values.
    for (i = -4; i <= 5; i++)
      for (j = 0; j <= 7; j++)
        for (k = -13; k <= 0; k++) {
          int_array where;
          where += i; where += j; where += k;
          int which = absolute_value(((i * 10 * 8) + (j * 8) + k)
              % fodder.length());
          if ( (which < 0) || (which >= fodder.length()) )
            deadly_error("conversion test", __WHERE__.s(),
                "index out of range");
          _variant_t to_store = fodder[which];
          if (!petard.put(where, to_store))
            deadly_error("conversion test", __WHERE__.s(), "failed to put");
        }

    _variant_t holding_array;
    petard.encapsulate(holding_array, true);

    // now test what the array should still have.
    for (i = -4; i <= 5; i++)
      for (j = 0; j <= 7; j++)
        for (k = -13; k <= 0; k++) {
          int_array where;
          where += i; where += j; where += k;
          int which = absolute_value(((i * 10 * 8) + (j * 8) + k)
              % fodder.length());
          _variant_t &expected = fodder[which];
          _variant_t got;
          petard.get(where, got);
          if (got != expected)
            deadly_error("conversion test", __WHERE__.s(),
                "wrong value in array");
        }

    com_array newbie(holding_array, false);  // suck in the data.
    if (holding_array.vt != VT_EMPTY)
      deadly_error("conversion test", __WHERE__.s(),
          "still had data when shouldn't");

    // now test what the new array should have.
    for (i = -4; i <= 5; i++)
      for (j = 0; j <= 7; j++)
        for (k = -13; k <= 0; k++) {
          int_array where;
          where += i; where += j; where += k;
          int which = absolute_value(((i * 10 * 8) + (j * 8) + k)
              % fodder.length());
          _variant_t &expected = fodder[which];
          _variant_t got;
          newbie.get(where, got);
          if (got != expected)
            deadly_error("conversion test", __WHERE__.s(),
                "wrong value in array");
        }

  }

  CoUninitialize();

  guards::alert_message("com_array:: works for those functions tested.");
  return 0;
}

