// server.cpp,v 1.6 2003/11/22 23:21:31 parsons Exp

// ============================================================================
//
// = LIBRARY
//    TAO/examples/Advanced/ch_18
//
// = FILENAME
//    server.cpp
//
// = AUTHORS
//   Source code used in TAO has been modified and adapted from the
//   code provided in the book, "Advanced CORBA Programming with C++"
//   by Michi Henning and Steve Vinoski. Copyright
//   1999. Addison-Wesley, Reading, MA.  Used with permission of
//   Addison-Wesley.
//
//   Modified for TAO by Mike Moran <mm4@cs.wustl.edu>
//
// ============================================================================

#include "server.h"
#include <algorithm>
#include "icp.h"
#include <orbsvcs/CosNamingC.h>
#include <strstream>
#include <iostream>
#include <fstream>

const char * Controller_oid = "Controller";

//----------------------------------------------------------------

template<class T>
typename T::_ptr_type
resolve_init (CORBA::ORB_ptr orb, const char * id)
{
  CORBA::Object_var obj;
  try {
    obj = orb->resolve_initial_references (id);
  }
  catch (const CORBA::ORB::InvalidName &) {
    throw;
  }
  catch (const CORBA::Exception & e) {
    cerr << "Cannot get initial reference for "
         << id << ": " << e << endl;
    throw 0;
  }
  assert (!CORBA::is_nil (obj.in ()));

  typename T::_var_type ref;
  try {
    ref = T::_narrow (obj.in ());
  }
  catch (const CORBA::Exception & e) {
    cerr << "Cannot narrow reference for "
         << id << ": " << e << endl;
    throw 0;
  }
  if (CORBA::is_nil (ref.in ())) {
    cerr << "Incorrect type of reference for "
         << id << endl;
    throw 0;
  }
  return ref._retn ();
}

//----------------------------------------------------------------

// Generic ostream inserter for exceptions. Inserts the exception
// name, if available, and the repository ID otherwise.

#if 0   // This inserter is not needed for TAO.

static ostream &
operator<< (ostream & os, const CORBA::Exception & e)
{
  CORBA::Any tmp;
  tmp <<= e;

  CORBA::TypeCode_var tc = tmp.type ();
  const char * p = tc->name ();
  if (*p != '\0')
    os << p;
  else
    os << tc->id ();
  return os;
}

#endif

//----------------------------------------------------------------

// Helper function to create object references.

static CCS::Thermometer_ptr
make_dref (PortableServer::POA_ptr poa, CCS::AssetType anum)
{
  // Convert asset number to OID.
  ostrstream ostr;
  ostr << anum << ends;
  char * anum_str = ostr.str ();
  PortableServer::ObjectId_var oid
    = PortableServer::string_to_ObjectId (anum_str);
  delete[] anum_str;

  // Look at the model via the network to determine
  // the repository ID.
  char buf[32];
  assert (ICP_get (anum, "model", buf, sizeof (buf)) == 0);
  const char * rep_id = strcmp (buf, "Sens-A-Temp") == 0
    ? "IDL:acme.com/CCS/Thermometer:1.0"
    : "IDL:acme.com/CCS/Thermostat:1.0";

  // Make a new reference.
  CORBA::Object_var obj
    = poa->create_reference_with_id (oid.in (), rep_id);
  return CCS::Thermometer::_narrow (obj.in ());
}

//----------------------------------------------------------------

Controller_impl * Thermometer_impl::m_ctrl;     // static member

// Helper function to read the model string from a device.

CCS::ModelType
Thermometer_impl::
get_model ()
{
  char buf[32];
  assert (ICP_get (m_anum, "model", buf, sizeof (buf)) == 0);
  return CORBA::string_dup (buf);
}

// Helper function to read the temperature from a device.

CCS::TempType
Thermometer_impl::
get_temp ()
{
  short temp;
  assert (ICP_get (m_anum, "temperature", &temp, sizeof (temp)) == 0);
  return temp;
}

// Helper function to read the location from a device.

CCS::LocType
Thermometer_impl::
get_loc ()
{
  char buf[32];
  assert (ICP_get (m_anum, "location", buf, sizeof (buf)) == 0);
  return CORBA::string_dup (buf);
}

// Helper function to set the location of a device.

void
Thermometer_impl::
set_loc (const char * loc)
{
  assert (ICP_set (m_anum, "location", loc) == 0);
}

// Constructor.

Thermometer_impl::
Thermometer_impl (CCS::AssetType anum) : m_anum (anum)
{
  m_ctrl->add_impl (anum, this);   // Add self to controller's set
}

// Destructor.

Thermometer_impl::
~Thermometer_impl ()
{
  if (m_ctrl->exists (m_anum))
    m_ctrl->add_impl (m_anum, 0);    // Clear servant pointer
}

// IDL model attribute.

CCS::ModelType
Thermometer_impl::
model () throw (CORBA::SystemException)
{
  return get_model ();
}

// IDL asset_num attribute.

CCS::AssetType
Thermometer_impl::
asset_num () throw (CORBA::SystemException)
{
  return m_anum;
}

// IDL temperature attribute.

CCS::TempType
Thermometer_impl::
temperature () throw (CORBA::SystemException)
{
  return get_temp ();
}

// IDL location attribute accessor.

CCS::LocType
Thermometer_impl::
location () throw (CORBA::SystemException)
{
  return get_loc ();
}

// IDL remove operation.

void
Thermometer_impl::
remove () throw (CORBA::SystemException)
{
  m_ctrl->remove_impl (m_anum);
  assert (ICP_offline (m_anum) == 0);
  //delete this;
}

// IDL location attribute modifier.

void
Thermometer_impl::
location (const char *loc) throw (CORBA::SystemException)
{
  set_loc (loc);
}

//----------------------------------------------------------------

// Helper function to get a thermostat's nominal temperature.

CCS::TempType
Thermostat_impl::
get_nominal_temp ()
{
  short temp;
  assert (ICP_get (m_anum, "nominal_temp", &temp, sizeof (temp)) == 0);
  return temp;
}

// Helper function to set a thermostat's nominal temperature.

CCS::TempType
Thermostat_impl::
set_nominal_temp (CCS::TempType new_temp)
throw (CCS::Thermostat::BadTemp)
{
  short old_temp;

  // We need to return the previous nominal temperature,
  // so we first read the current nominal temperature before
  // changing it.
  assert (
          ICP_get (
                   m_anum, "nominal_temp", &old_temp, sizeof (old_temp)
                   ) == 0
          );

  // Now set the nominal temperature to the new value.
  if (ICP_set (m_anum, "nominal_temp", &new_temp) != 0) {

    // If ICP_set () failed, read this thermostat's minimum
    // and maximum so we can initialize the BadTemp exception.
    CCS::Thermostat::BtData btd;
    ICP_get (
             m_anum, "MIN_TEMP",
             &btd.min_permitted, sizeof (btd.min_permitted)
             );
    ICP_get (
             m_anum, "MAX_TEMP",
             &btd.max_permitted, sizeof (btd.max_permitted)
             );
    btd.requested = new_temp;
    btd.error_msg = CORBA::string_dup (
                                       new_temp > btd.max_permitted ? "Too hot" : "Too cold"
                                       );
    throw CCS::Thermostat::BadTemp (btd);
  }
  return old_temp;
}

// Constructor.

Thermostat_impl::
Thermostat_impl (CCS::AssetType anum) : Thermometer_impl (anum)
{
  // Intentionally empty.
}

// Destructor.

Thermostat_impl::
~Thermostat_impl ()
{
  // Intentionally empty.
}

// IDL get_nominal operation.

CCS::TempType
Thermostat_impl::
get_nominal () throw (CORBA::SystemException)
{
  return get_nominal_temp ();
}

// IDL set_nominal operation.

CCS::TempType
Thermostat_impl::
set_nominal (CCS::TempType new_temp)
throw (CORBA::SystemException, CCS::Thermostat::BadTemp)
{
  return set_nominal_temp (new_temp);
}

//----------------------------------------------------------------

// Helper function to add an entry to the asset map.

void
Controller_impl::
add_impl (CCS::AssetType anum, Thermometer_impl * tip)
{
  m_assets[anum] = tip;
}

// Helper function to remove an entry from the asset map.

void
Controller_impl::
remove_impl (CCS::AssetType anum)
{
    m_assets.erase (anum);
}

// Helper function to locate a servant in the asset map.

bool
Controller_impl::
exists (CCS::AssetType anum)
{
    return m_assets.find (anum) != m_assets.end ();
}

// Constructor

Controller_impl::
Controller_impl (
    PortableServer::POA_ptr poa,
    const char *            asset_file
) throw (int) : m_poa (PortableServer::POA::_duplicate (poa)),
               m_asset_file (asset_file)
{
    fstream afile (m_asset_file.in (), ios::in|ios::out, 0666);
    if (!afile) {
        cerr << "Cannot open " << m_asset_file.in () << endl;
        throw 0;
    }
    CCS::AssetType anum;
    while (afile >> anum)
        m_assets[anum] = 0;
    //afile.close ();
    //if (!afile) {
    //    cerr << "Cannot close " << m_asset_file << endl;
    //    throw 0;
    //}
}

// Destructor

Controller_impl::
~Controller_impl ()
{
    // Write out the current set of asset numbers
    // and clean up all servant instances.
    ofstream afile (m_asset_file.in ());
    if (!afile) {
        cerr << "Cannot open " << m_asset_file.in () << endl;
        assert (0);
    }
    AssetMap::iterator i;
    for (i = m_assets.begin (); i != m_assets.end (); i++) {
        afile << i->first << endl;
        if (!afile) {
            cerr << "Cannot update " << m_asset_file.in () << endl;
            assert (0);
        }
        delete i->second;
    }
    //afile.close ();
    //if (!afile) {
    //    cerr << "Cannot close " << m_asset_file << endl;
    //    assert (0);
    //}
}

CCS::Thermometer_ptr
Controller_impl::
create_thermometer (CCS::AssetType anum, const char * loc)
throw (CORBA::SystemException, CCS::Controller::DuplicateAsset)
{
    if (anum % 2 == 0)
        throw CORBA::BAD_PARAM ();   // Thermometers have odd numbers
    if (exists (anum))
        throw CCS::Controller::DuplicateAsset ();

    assert (ICP_online (anum) == 0);
    assert (ICP_set (anum, "location", loc) == 0);
    add_impl (anum, 0);
    return make_dref (m_poa.in (), anum);
}

CCS::Thermostat_ptr
Controller_impl::
create_thermostat (
    CCS::AssetType      anum,
    const char*     loc,
    CCS::TempType       temp)
throw (
    CORBA::SystemException,
    CCS::Controller::DuplicateAsset,
    CCS::Thermostat::BadTemp)
{
    if (anum % 2 != 0)
        throw CORBA::BAD_PARAM ();   // Thermostats have even numbers
    if (exists (anum))
        throw CCS::Controller::DuplicateAsset ();

    assert (ICP_online (anum) == 0);
    assert (ICP_set (anum, "location", loc) == 0);
    // Set the nominal temperature.
    if (ICP_set (anum, "nominal_temp", &temp) != 0) {

        // If ICP_set () failed, read this thermostat's minimum
        // and maximum so we can initialize the BadTemp exception.
        CCS::Thermostat::BtData btd;
        ICP_get (
            anum, "MIN_TEMP",
            &btd.min_permitted, sizeof (btd.min_permitted)
        );
        ICP_get (
            anum, "MAX_TEMP",
            &btd.max_permitted, sizeof (btd.max_permitted)
        );
        btd.requested = temp;
        btd.error_msg = CORBA::string_dup (
            temp > btd.max_permitted ? "Too hot" : "Too cold"
        );
        ICP_offline (anum);
        throw CCS::Thermostat::BadTemp (btd);
    }

    add_impl (anum, 0);
    CORBA::Object_var obj = make_dref (m_poa.in (), anum);
    return CCS::Thermostat::_narrow (obj.in ());
}

// IDL list operation.

CCS::Controller::ThermometerSeq *
Controller_impl::
list () throw (CORBA::SystemException)
{
    // Create a new thermometer sequence. Because we know
    // the number of elements we will put onto the sequence,
    // we use the maximum constructor.
    CCS::Controller::ThermometerSeq_var listv
        = new CCS::Controller::ThermometerSeq (m_assets.size ());
    listv->length (m_assets.size ());

    // Loop over the m_assets set and create a
    // reference for each device.
    CORBA::ULong count = 0;
    AssetMap::iterator i;
    for (i = m_assets.begin (); i != m_assets.end (); i++)
        listv[count++] = make_dref (m_poa.in (), i->first);
    return listv._retn ();
}

// IDL change operation.

void
Controller_impl::
change (
    const CCS::Controller::ThermostatSeq &  tlist,
    CORBA::Short                            delta
) throw (CORBA::SystemException, CCS::Controller::EChange)
{
    CCS::Controller::EChange ec;    // Just in case we need it

    // We cannot add a delta value to a thermostat's temperature
    // directly, so for each thermostat, we read the nominal
    // temperature, add the delta value to it, and write
    // it back again.
    for (CORBA::ULong i = 0; i < tlist.length (); i++) {
        if (CORBA::is_nil (tlist[i]))
            continue;                       // Skip nil references

        // Read nominal temp and update it.
        CCS::TempType tnom = tlist[i]->get_nominal ();
        tnom += delta;
        try {
            tlist[i]->set_nominal (tnom);
        }
        catch (const CCS::Thermostat::BadTemp &bt) {
            // If the update failed because the temperature
            // is out of range, we add the thermostat's info
            // to the errors sequence.
            CORBA::ULong len = ec.errors.length ();
            ec.errors.length (len + 1);
            ec.errors[len].tmstat_ref = tlist[i].in ();
            ec.errors[len].info = bt.details;
        }
    }

    // If we encountered errors in the above loop,
    // we will have added elements to the errors sequence.
    if (ec.errors.length () != 0)
        throw ec;
}

// IDL find operation

void
Controller_impl::
find (CCS::Controller::SearchSeq & slist)
throw (CORBA::SystemException)
{
    // Loop over input list and lookup each device.
    CORBA::ULong listlen = slist.length ();
    for (CORBA::ULong i = 0; i < listlen; i++) {

        AssetMap::iterator where;   // Iterator for asset set
        int num_found = 0;          // Num matched per iteration

        // Assume we will not find a matching device.
        slist[i].device = CCS::Thermometer::_nil ();

        // Work out whether we are searching by asset,
        // model, or location.
        CCS::Controller::SearchCriterion sc = slist[i].key._d ();
        if (sc == CCS::Controller::ASSET) {
            // Search for matching asset number.
            where = m_assets.find (slist[i].key.asset_num ());
            if (where != m_assets.end ())
                slist[i].device = make_dref (m_poa.in (), where->first);
        } else {
            // Search for model or location string.
            const char *search_str;
            if (sc == CCS::Controller::LOCATION)
                search_str = slist[i].key.loc ();
            else
                search_str = slist[i].key.model_desc ();

            // Find first matching device (if any).
            where = find_if (
                        m_assets.begin (), m_assets.end (),
                        StrFinder (sc, search_str)
                    );

            // While there are matches...
            while (where != m_assets.end ()) {
                if (num_found == 0) {
                    // First match overwrites reference
                    // in search record.
                    slist[i].device = make_dref (m_poa.in (), where->first);
                } else {
                    // Further matches each append a new
                    // element to the search sequence.
                    CORBA::ULong len = slist.length ();
                    slist.length (len + 1);
                    slist[len].key = slist[i].key;
                    slist[len].device = make_dref (m_poa.in (), where->first);
                }
                num_found++;

                // Find next matching device with this key.
                where = find_if (
                            ++where, m_assets.end (),
                            StrFinder (sc, search_str)
                        );
            }
        }
    }
}

//----------------------------------------------------------------

DeviceLocator_impl::
DeviceLocator_impl (Controller_impl * ctrl) : m_ctrl (ctrl)
{
    // Intentionally empty
}

PortableServer::Servant
DeviceLocator_impl::
preinvoke (
    const PortableServer::ObjectId & oid,
    PortableServer::POA_ptr          /* poa */,
    const char *                     operation,
    void * &                         /* cookie */
) throw (CORBA::SystemException, PortableServer::ForwardRequest)
{
    // Convert object id into asset number.
    CORBA::String_var oid_string;
    try {
        oid_string = PortableServer::ObjectId_to_string (oid);
    } catch (const CORBA::BAD_PARAM &) {
        throw CORBA::OBJECT_NOT_EXIST ();
    }

    if (strcmp (oid_string.in (), Controller_oid) == 0)
        return m_ctrl;

    istrstream istr (oid_string.in ());
    CCS::AssetType anum;
    istr >> anum;
    if (istr.fail ())
        throw CORBA::OBJECT_NOT_EXIST ();

    // Check whether the device is known.
    if (!m_ctrl->exists (anum))
        throw CORBA::OBJECT_NOT_EXIST ();

    // Look at the object map to find out whether
    // we have a servant in memory.
    Thermometer_impl * servant;
    ActiveObjectMap::iterator servant_pos = m_aom.find (anum);
    if (servant_pos  == m_aom.end ()) {
        // No servant in memory. If evictor queue is full,
        // evict servant at head of queue.
        if (m_eq.size () == MAX_EQ_SIZE) {
            servant = m_eq.back ();
            m_aom.erase (servant->m_anum);
            m_eq.pop_back ();
            delete servant;
        }
        // Instantiate correct type of servant.
        char buf[32];
        assert (ICP_get (anum, "model", buf, sizeof (buf)) == 0);
        if (strcmp (buf, "Sens-A-Temp") == 0)
            servant = new Thermometer_impl (anum);
        else
            servant = new Thermostat_impl (anum);
    } else {
        // Servant already in memory.
        servant = * (servant_pos->second);   // Remember servant
        m_eq.erase (servant_pos->second);    // Remove from queue

        // If operation is "remove", also remove entry from
        // active object map -- the object is about to be deleted.
        if (strcmp (operation, "remove") == 0)
            m_aom.erase (servant_pos);
    }

    // We found a servant, or just instantiated it.
    // If the operation is not a remove, move
    // the servant to the tail of the evictor queue
    // and update its queue position in the map.
    if (strcmp (operation, "remove") != 0) {
        m_eq.push_front (servant);
        m_aom[anum] = m_eq.begin ();
    }

    return servant;
}

//----------------------------------------------------------------

int
main (int argc, char * argv[])
{
    CORBA::ORB_var orb;

    try {
        // Initialize orb
        orb = CORBA::ORB_init (argc, argv);

        // Get reference to Root POA.
        CORBA::Object_var obj
            = orb->resolve_initial_references ("RootPOA");
        PortableServer::POA_var poa
            = PortableServer::POA::_narrow (obj.in ());

        // Get POA manager
        PortableServer::POAManager_var poa_mgr = poa->the_POAManager ();

        // Create a policy list. We use persistent objects with
        // user-assigned IDs, and explicit activation.
        CORBA::PolicyList policy_list;
        policy_list.length (6);
        policy_list[0] = poa->create_lifespan_policy (
                            PortableServer::TRANSIENT   // REVISIT
                         );
        policy_list[1] = poa->create_id_assignment_policy (
                            PortableServer::USER_ID
                         );
        policy_list[2] = poa->create_implicit_activation_policy (
                            PortableServer::NO_IMPLICIT_ACTIVATION
                         );
        policy_list[3] = poa->create_request_processing_policy (
                            PortableServer::USE_SERVANT_MANAGER
                         );
        policy_list[4] = poa->create_servant_retention_policy (
                            PortableServer::NON_RETAIN
                         );
        policy_list[5] = poa->create_thread_policy (
                            PortableServer::SINGLE_THREAD_MODEL
                         );

        // Create a POA for all CCS elements.
        PortableServer::POA_var ccs_poa
            = poa->create_POA ("CCS_POA", poa_mgr.in (), policy_list);

        // Create a controller and set static m_ctrl member
        // for thermostats and thermometers.
        Controller_impl ctrl_servant (ccs_poa.in (), "/tmp/CCS_assets");
        Thermometer_impl::m_ctrl = &ctrl_servant;

        // Create a reference for the controller and
        // create the corresponding CORBA object.
        PortableServer::ObjectId_var oid
            = PortableServer::string_to_ObjectId (Controller_oid);
        CORBA::Object_var ctrl
            = ccs_poa->create_reference_with_id (
                oid.in (), "IDL:acme.com/CCS/Controller:1.0"
              );

        // Get reference to initial naming context.
        CosNaming::NamingContext_var inc
            = resolve_init<CosNaming::NamingContext> (
                    orb.in (), "NameService"
              );

        // Attempt to create CCS context.
        CosNaming::Name n;
        n.length (1);
        n[0].id = CORBA::string_dup ("CCS");
        try {
            CosNaming::NamingContext_var nc
                = inc->bind_new_context (n);
        } catch (const CosNaming::NamingContext::AlreadyBound &) {
            // Fine, CCS context already exists.
        }

        // Force binding of controller reference to make
        // sure it is always up-to-date.
        n.length (2);
        n[1].id = CORBA::string_dup ("Controller");
        inc->rebind (n, ctrl.in ());

        // Instantiate the servant locator for devices.
        PortableServer::ServantManager_var locator =
          new DeviceLocator_impl (&ctrl_servant);

        // Set servant locator.
        ccs_poa->set_servant_manager (locator.in ());

        // Activate the POA manager.
        poa_mgr->activate ();

        // Accept requests
        orb->run ();
    }
    catch (const CORBA::Exception & e) {
        cerr << "Uncaught CORBA exception: " << e << endl;
        return 1;
    }
    catch (...) {
        assert (0);  // Uncaught exception, dump core
    }
    return 0;
}
