/*
    This file is part of GNU APL, a free implementation of the
    ISO/IEC Standard 13751, "Programming Language APL, Extended"

    Copyright (C) 2008-2015  Dr. Jürgen Sauermann

    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 <errno.h>
#include <fcntl.h>           /* For O_* constants */
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>

#include <iomanip>

#include "Backtrace.hh"
#include "Logging.hh"
#include "PrintOperator.hh"
#include "ProcessorID.hh"
#include "Svar_record.hh"
#include "Svar_signals.hh"

extern ostream & get_CERR();

//=============================================================================
const char *
event_name(Svar_event ev)
{
   switch(ev)
      {
        case SVE_NO_EVENTS:          return "(no event)";
        case SVE_OFFER_MISMATCH:     return "offer mismatch";
        case SVE_OFFER_MATCHED:      return "offer matched";
        case SVE_OFFER_RETRACT:      return "offer retracted";
        case SVE_ACCESS_CONTROL:     return "access control changed";
        case SVE_USE_BY_OFF_SUCCESS: return "use by offering successful";
        case SVE_USE_BY_OFF_FAILED:  return "use by offering failed";
        case SVE_SET_BY_OFF_SUCCESS: return "set by offering successful";
        case SVE_SET_BY_OFF_FAILED:  return "set by offering failed";
        case SVE_USE_BY_ACC_SUCCESS: return "use by accepting successful";
        case SVE_USE_BY_ACC_FAILED:  return "use by accepting failed";
        case SVE_SET_BY_ACC_SUCCESS: return "set by accepting successful";
        case SVE_SET_BY_ACC_FAILED:  return "set by accepting failed";
      }

   return "(unknown event)";
}
//-----------------------------------------------------------------------------
ostream &
Svar_partner::print(ostream & out) const
{
   out << setw(5) << id.proc;
   if (id.parent)   out << "," << left << setw(5) << id.parent << right;
   else             out << "      ";

   out << "│" << setw(3) << tcp_fd << "│"
       << hex << uppercase << setfill('0') << setw(2) << flags
       << dec << nouppercase << setfill(' ');

   return out;
}
//-----------------------------------------------------------------------------
void
Svar_record::remove_accepting()
{
   accepting.clear();
}
//-----------------------------------------------------------------------------
void
Svar_record::remove_offering()
{
const AP_num3 offered_to = offering.id;

   offering = accepting;
   accepting.clear();
   accepting.id = offered_to;
}
//-----------------------------------------------------------------------------
SV_Coupling
Svar_record::retract()
{
const SV_Coupling old_coupling = get_coupling();

   if (ProcessorID::get_id() == offering.id)         remove_offering();
   else if (ProcessorID::get_id() == accepting.id)   remove_accepting();
   else
      {
        bad_proc("Svar_record::retract", ProcessorID::get_id());
        return NO_COUPLING;
      }

   // clear variable if last partner has retracted, or else send signal
   // to the remaining partner.
   //
   if (get_coupling() == NO_COUPLING)   // retract of a non-coupled variable
      {
        clear();
      }

   return old_coupling;
}
//-----------------------------------------------------------------------------
bool
Svar_record::is_ws_to_ws()   const
{
   if (get_coupling() != SV_COUPLED)   return offering.id.proc >= AP_FIRST_USER;

   return offering.id.proc  >= AP_FIRST_USER &&
          accepting.id.proc >= AP_FIRST_USER;
}
//-----------------------------------------------------------------------------
bool
Svar_record::match_name(const uint32_t * UCS_other) const
{
  // compare INCLUDING terminating 0;
  for (int v = 0; v < (MAX_SVAR_NAMELEN); ++v)
      {
        if (varname[v] != UCS_other[v])
           {
             if (varname[v]   == 0x22C6)   return true;   // ⋆
             if (varname[v]   == '*')      return true;
             if (UCS_other[v] == 0x22C6)   return true;   // ⋆
             if (UCS_other[v] == '*')      return true;
             return false;
           }
        if (varname[v] == 0)              return true;   // short name
      }

   return true;                                           // long name
}
//-----------------------------------------------------------------------------
Svar_Control
mirror(int flags)
{
int ret = 0;
   if (flags & SET_BY_OFF)   ret |= SET_BY_ACC;
   if (flags & SET_BY_ACC)   ret |= SET_BY_OFF;
   if (flags & USE_BY_OFF)   ret |= USE_BY_ACC;
   if (flags & USE_BY_ACC)   ret |= USE_BY_OFF;
   return Svar_Control(ret);
}
//-----------------------------------------------------------------------------
Svar_Control
Svar_record::get_control() const
{
int ctl = offering.get_control() | accepting.get_control();

   if (ProcessorID::get_id() == accepting.id)   ctl = mirror(ctl);
   return Svar_Control(ctl);
}
//-----------------------------------------------------------------------------
void
Svar_record::set_control(Svar_Control ctl)
{
   Log(LOG_shared_variables)
      {
        get_CERR() << "set_control(" << ctl << ") on ";
        print_name(get_CERR());
        get_CERR() << " by " << ProcessorID::get_id().proc << endl;
      }

   if (ProcessorID::get_id() == offering.id)
      {
        offering.set_control(ctl);
        if (get_coupling() == SV_COUPLED)   // fully coupled: inform peer
           {
              accepting.events = Svar_event
                                 (accepting.events | SVE_ACCESS_CONTROL);
           }
      }
   else if (ProcessorID::get_id() == accepting.id)   // hence fully coupled
      {
        accepting.set_control(mirror(ctl));
        offering.events = Svar_event(offering.events | SVE_ACCESS_CONTROL);
      }
   else
      {
        bad_proc(__FUNCTION__, ProcessorID::get_id());
      }
}
//-----------------------------------------------------------------------------
Svar_state
Svar_record::get_state() const
{
   return state;
}
//-----------------------------------------------------------------------------
void
Svar_record::set_state(bool used, const char * loc)
{
usleep(50000);

   Log(LOG_shared_variables)
      {
        const char * op = used ? "used" : "set";
        get_CERR() << "set_state(" << op << ") on ";
        print_name(get_CERR());
        get_CERR() << " by " << ProcessorID::get_id().proc
                   << " at " << loc << endl;
      }

   // the control vector as seen by the offering side
   //
const int control = offering.get_control() | accepting.get_control();
Svar_event event = SVE_NO_EVENTS;
Svar_partner * peer = 0;

   if (ProcessorID::get_id() == offering.id)
      {
        peer = &offering;
        offering.events = SVE_NO_EVENTS;   // clear events

        if (used)   // offering has used the variable (unless read-back)
           {
             if (state == SVS_OFF_HAS_SET)   ;   // read-back
             else
                {
                  if (control & USE_BY_OFF)   event = SVE_USE_BY_OFF_SUCCESS;
                  state = SVS_IDLE;
                }
           }
        else        // offering has set the variable
           {
             if (control & SET_BY_OFF)   event = SVE_SET_BY_OFF_SUCCESS;
             state = SVS_OFF_HAS_SET;
           }
      }
   else if (ProcessorID::get_id() == accepting.id)
      {
        peer = &offering;
        accepting.events = SVE_NO_EVENTS;   // clear events

        if (used)   // accepting has used the variable (unless read-back)
           {
             if (state == SVS_ACC_HAS_SET)   ; // read-back
             else
                {
                  if (control & USE_BY_ACC)   event = SVE_USE_BY_ACC_SUCCESS;
                  state = SVS_IDLE;
                }
           }
        else        // accepting has set the variable
           {
             if (control & SET_BY_ACC)   event = SVE_SET_BY_ACC_SUCCESS;
             state = SVS_ACC_HAS_SET;
           }
      }
   else   // only the partners should call set_state();
      {
        bad_proc(__FUNCTION__, ProcessorID::get_id());
        return;
      }

   // if access was restricted then inform peer
   //
   if (peer && event != SVE_NO_EVENTS)
      {
        peer->events = Svar_event(peer->events | event);
      }
}
//-----------------------------------------------------------------------------
bool
Svar_record::may_use(int attempt)
{
   // control restriction as seen by the offering partner
   //
const int control = offering.get_control() | accepting.get_control();
const int restriction = control & state;

   if (ProcessorID::get_id() == offering.id)
      {
        if ((restriction & USE_BY_OFF) == 0)   return true;   // no restriction

        if (accepting.is_active() && (attempt == 0))
           {
             accepting.events = Svar_event(accepting.events |
                                           SVE_USE_BY_OFF_FAILED);
           }
        return false;
      }

   if (ProcessorID::get_id() == accepting.id)
      {
        if ((restriction & USE_BY_ACC) == 0)   return true;   // no restriction

        if (offering.is_active() && (attempt == 0))   // maybe send event to peer
           {
             offering.events = Svar_event(offering.events |
                                          SVE_USE_BY_ACC_FAILED);
           }
        return false;
      }

   bad_proc(__FUNCTION__, ProcessorID::get_id());
   return false;
}
//-----------------------------------------------------------------------------
bool
Svar_record::may_set(int attempt)
{
   // control restriction as seen by the offering partner
   //
const int control = offering.get_control() | accepting.get_control();
const int restriction = control & state;

   if (ProcessorID::get_id() == offering.id)
      {
        if ((restriction & SET_BY_OFF) == 0)   return true;   // no restriction

        if (accepting.is_active() && (attempt == 0))   // maybe send event to peer
           {
             accepting.events = Svar_event(accepting.events |
                                           SVE_SET_BY_OFF_FAILED);
           }
        return false;
      }

   if (ProcessorID::get_id() == accepting.id)
      {
        if ((restriction & SET_BY_ACC) == 0)   return true;   // no restriction

        if (offering.is_active() && (attempt == 0))   // maybe send event to peer
           {
             offering.events = Svar_event(offering.events |
                                          SVE_SET_BY_ACC_FAILED);
           }
        return false;
      }

   bad_proc(__FUNCTION__, ProcessorID::get_id());
   return false;
}
//-----------------------------------------------------------------------------
void
Svar_record::bad_proc(const char * function, const AP_num3 & id) const
{
   get_CERR() << function << "(): proc " << id.proc
        << " does not match offering proc " << offering.id.proc
        << " nor accepting proc " << accepting.id.proc << endl;
}
//-----------------------------------------------------------------------------
void
Svar_record::print(ostream & out) const
{
const Svar_state st = get_state();
   out << "║" << setw(5) << (key & 0xFFFF) << "│" << get_coupling() << "║";
   offering.print(out)  << "║";
   accepting.print(out) << "║";
   if (st & SET_BY_OFF)   out << "1";    else   out << "0";
   if (st & SET_BY_ACC)   out << "1";    else   out << "0";
   if (st & USE_BY_OFF)   out << "1";    else   out << "0";
   if (st & USE_BY_ACC)   out << "1│";   else   out << "0│";
   print_name(out, varname, 10) << "║" << endl;
}
//-----------------------------------------------------------------------------
ostream &
Svar_record::print_name(ostream & out, const uint32_t * name, int len)
{
   while (*name)
       {
         uint32_t uni = *name++;
         if (uni < 0x80)
           {
             out << char(uni);
           }
        else if (uni < 0x800)
           {
             const uint8_t b1 = uni & 0x3F;   uni >>= 6;
             out << char(uni | 0xC0)
                 << char(b1  | 0x80);
           }
        else if (uni < 0x10000)
           {
             const uint8_t b2 = uni & 0x3F;   uni >>= 6;
             const uint8_t b1 = uni & 0x3F;   uni >>= 6;
             out << char(uni | 0xE0)
                 << char(b1  | 0x80)
                 << char(b2  | 0x80);
           }
        else if (uni < 0x110000)
           {
             const uint8_t b3 = uni & 0x3F;   uni >>= 6;
             const uint8_t b2 = uni & 0x3F;   uni >>= 6;
             const uint8_t b1 = uni & 0x3F;   uni >>= 6;
             out << char(uni | 0xE0)
                 << char(b1  | 0x80)
                 << char(b2  | 0x80)
                 << char(b3  | 0x80);
           }

        --len;
       }

   while (len-- > 0)   out << " ";

   return out;
}
//-----------------------------------------------------------------------------
