///
/// \file    PVCtrlHolder.h
/// \author  Jan Chrin, PSI
/// \date    November 2014
/// \version CAFE 1.0.0
///

#ifndef PVCTRLHOLDER_H
#define PVCTRLHOLDER_H

#include <PVHolder.h>


/**
* \class PVCtrlHolder
* \brief This class is the holder of values associated with the
* EPICS DBR_CTRL_(dataType) control structure of a given handle/pv
*/
class PVCtrlHolder : public PVHolder
{

    friend class CAFE;
    friend class Connect;
    friend class Conduit;
    friend struct change_dataBufferPVCtrlHolder;
    friend struct change_dataBufferSize_CTRL;
    friend struct change_connectionHandlerArgs;
    friend struct change_pvAlias;

private:

    short precision;
    char  units[MAX_UNITS_SIZE];

    CAFE_DATATYPE_UNION RISC_pad;
    CAFE_DATATYPE_UNION upperDispLimit;
    CAFE_DATATYPE_UNION lowerDispLimit;
    CAFE_DATATYPE_UNION upperAlarmLimit;
    CAFE_DATATYPE_UNION upperWarningLimit;
    CAFE_DATATYPE_UNION lowerWarningLimit;
    CAFE_DATATYPE_UNION lowerAlarmLimit;
    CAFE_DATATYPE_UNION upperCtrlLimit;
    CAFE_DATATYPE_UNION lowerCtrlLimit;

    Helper helper;


public:

    PVCtrlHolder()
    {
        alarmStatus=0;
        alarmSeverity=0;
        precision=0;
        status=ECA_NORMAL;
        nelem=1;
        size=1;
        rule=true;
        beamEventNo=0;
        hasAlarm=true;
        noStr=0;
        dataType=(CAFE_DATATYPE) CAFE_NO_ACCESS;
        dataTypeNative=(CAFE_DATATYPE) CAFE_NO_ACCESS;
        strcpy(pv,"");
        strcpy(pvAlias,"");
        strcpy(device,"");
        strcpy(attrib,"");
        strcpy(units,"");

        val.reset( new CAFE_DATATYPE_UNION[nelem] );
        val[0].d=0.00;
    };

    PVCtrlHolder(unsigned int  _sizeOfArray)
    {
        alarmStatus=0;
        alarmSeverity=0;
        precision=0;
        status=ECA_NORMAL;
        nelem=_sizeOfArray;
        size=_sizeOfArray;
        rule=true;
        beamEventNo=0;
        hasAlarm=true;
        noStr=0;
        dataType=(CAFE_DATATYPE) CAFE_NO_ACCESS;
        dataTypeNative=(CAFE_DATATYPE) CAFE_NO_ACCESS;
        strcpy(pv,"");
        strcpy(pvAlias,"");
        strcpy(device,"");
        strcpy(attrib,"");
        strcpy(units,"");

        val.reset( new CAFE_DATATYPE_UNION[nelem] );

        for (unsigned int  i=0; i<nelem; ++i)
        {
            val[i].d=0.0;
        };
    };

    ~PVCtrlHolder()
    {
        val.reset();
    };

    void setHasAlarm(bool a)
    {
        hasAlarm=a;
        return;
    };

    unsigned int  setNelem (unsigned int  _nelem)
    {
        _nelem>0 ? nelem=_nelem : nelem=1;

        if (_nelem>size)
        {

            size=_nelem;

            val.reset( new CAFE_DATATYPE_UNION[size] );
        }

        return nelem;
    };

    short getPrecision()       const
    {
        return precision;
    }

    const char * getUnits()    const
    {
        return units;
    }

    std::string getUnitsAsString()    const
    {
        return (std::string) units;
    }

    /*Defined in PVHolder.h
    short getNoEnumStrings ()  const
    {
        return noStr;
    };
    char * getEnumString(short indx) const
    {
      return (char *) strs[indx];
    };
    */
   
    std::vector<std::string>  getEnumStrings() const
    {

        std::vector<std::string> vEnumStrings;

        vEnumStrings.reserve(noStr>0?noStr:1);
        for ( short i=0; i<noStr; ++i)
        {
            vEnumStrings.push_back(strs[i]);
        }
        return vEnumStrings;
    };


    short getEnumFromString(std::string enumString)
    {

        short returnValue=INVALID_ENUM_RETURN_VALUE;

        for ( short i=0; i<noStr; ++i)
        {
            if (strcmp(enumString.c_str(), strs[i])==0)
            {
                return i;
            }
        }


        char pvStripped[MAX_ENUM_STRING_SIZE];
        helper.removeLeadingAndTrailingSpaces((char *) enumString.c_str(), pvStripped);

        for ( short i=0; i<noStr; ++i)
        {
            if (strcmp(pvStripped, strs[i])==0)
            {
                return i;
            }
        }

        if (dataType==CAFE_ENUM) {   
            std::cout << "*** WARNING FROM PvCtrlHolder.h ***" << std::endl;
            std::cout << "*** Method getEnumFromString(string enumString) ***" << std::endl;
            std::cout << "The given input string '" << enumString << "' was not recognized! "  << std::endl;
            if (noStr > 0) {
                std::cout << "Valid values are: " << std::endl;       
                for ( short i=0; i<noStr; ++i)
                {
                    std::cout << i << ":" << strs[i] << std::endl;
                }
            }
        }
        else
	{ 
            std::cout << "*** WARNING FROM PvCtrlHolder.h ***" << std::endl;
            std::cout << "*** Method getEnumFromString(string enumString) ***" << std::endl;
            std::cout << "Native DataType " << (CAFEDataTypeCode().asString((CAFE_DATATYPE) dataType)).c_str() 
                      << " is not the enumerated type, DBF_ENUM " << std::endl;
        }

        return returnValue;

    }

    std::string getStringFromEnum(unsigned short enumValue) const
    {

        std::string returnValue=INVALID_ENUM_RETURN_STRING;

        if (enumValue<noStr)
        {
            return (std::string) strs[enumValue];
        }
        else
        {
	    if (dataType==CAFE_ENUM) { 

                std::cout << "*** WARNING FROM PvCtrlHolder.h ***" << std::endl;
                std::cout << "*** Method getStringFromEnum(unsigned short enumValue) ***" << std::endl;
                std::cout << "The given input index [" << enumValue << "] exceeds the number of enum states " << noStr << std::endl;
                if (noStr > 0)
	        {
                    std::cout << "Valid values are: " << std::endl;
                    for ( short i=0; i<noStr; ++i)
                    {
                        std::cout << i << ":" << strs[i] << std::endl;
                    }
	        }
            }
            else
	    { 
                std::cout << "*** WARNING FROM PvCtrlHolder.h ***" << std::endl;
                std::cout << "*** Method getStringFromEnum(unsigned short enumValue) ***" << std::endl;
                std::cout << "Native DataType " << (CAFEDataTypeCode().asString((CAFE_DATATYPE) dataType)).c_str() 
                          << " is not the enumerated type, DBF_ENUM " << std::endl;                        
            }

            return returnValue; 

            /*
            if (enumValue<MAX_ENUM_STATES)
            {
                return (std::string) strs[enumValue];
            }
            else
            {
                return returnValue;
            }
            */
        }
    }



    CafeDataTypeHelper getRISC_pad()
    {
        CafeDataTypeHelper cdth(RISC_pad, dataType);
        return cdth;
    }
    CafeDataTypeHelper getUpperDispLimit()
    {
        CafeDataTypeHelper cdth(upperDispLimit, dataType);
        return cdth;
    }
    CafeDataTypeHelper getLowerDispLimit()
    {
        CafeDataTypeHelper cdth(lowerDispLimit, dataType);
        return cdth;
    }
    CafeDataTypeHelper getUpperAlarmLimit()
    {
        CafeDataTypeHelper cdth(upperAlarmLimit, dataType);
        return cdth;
    }
    CafeDataTypeHelper getUpperWarningLimit()
    {
        CafeDataTypeHelper cdth(upperWarningLimit, dataType);
        return cdth;
    }
    CafeDataTypeHelper getLowerWarningLimit()
    {
        CafeDataTypeHelper cdth(lowerWarningLimit, dataType);
        return cdth;
    }
    CafeDataTypeHelper getLowerAlarmLimit()
    {
        CafeDataTypeHelper cdth(lowerAlarmLimit, dataType);
        return cdth;
    }
    CafeDataTypeHelper getUpperCtrlLimit()
    {
        CafeDataTypeHelper cdth(upperCtrlLimit, dataType);
        return cdth;
    }
    CafeDataTypeHelper getLowerCtrlLimit()
    {
        CafeDataTypeHelper cdth(lowerCtrlLimit, dataType);
        return cdth;
    }



    std::string getUpperDispLimit_AsString()
    {
        return getAsString(upperDispLimit);
    }
    std::string getLowerDispLimit_AsString()
    {
        return getAsString(lowerDispLimit);
    }
    std::string getUpperAlarmLimit_AsString()
    {
        return getAsString(upperAlarmLimit);
    }
    std::string getUpperWarningLimit_AsString()
    {
        return getAsString(upperWarningLimit);
    }
    std::string getLowerWarningLimit_AsString()
    {
        return getAsString(lowerWarningLimit);
    }
    std::string getLowerAlarmLimit_AsString()
    {
        return getAsString(lowerAlarmLimit);
    }
    std::string getUpperCtrlLimit_AsString()
    {
        return getAsString(upperCtrlLimit);
    }
    std::string getLowerCtrlLimit_AsString()
    {
        return getAsString(lowerCtrlLimit);
    }

    double getUpperDispLimit_AsDouble()
    {
        return getAsDouble(upperDispLimit);
    }
    double getLowerDispLimit_AsDouble()
    {
        return getAsDouble(lowerDispLimit);
    }
    double getUpperAlarmLimit_AsDouble()
    {
        return getAsDouble(upperAlarmLimit);
    }
    double getUpperWarningLimit_AsDouble()
    {
        return getAsDouble(upperWarningLimit);
    }
    double getLowerWarningLimit_AsDouble()
    {
        return getAsDouble(lowerWarningLimit);
    }
    double getLowerAlarmLimit_AsDouble()
    {
        return getAsDouble(lowerAlarmLimit);
    }
    double getUpperCtrlLimit_AsDouble()
    {
        return getAsDouble(upperCtrlLimit);
    }
    double getLowerCtrlLimit_AsDouble()
    {
        return getAsDouble(lowerCtrlLimit);
    }

    void print()
    {
        print(nelem) ;
    }


    void print(unsigned int  nelemToPrint)
    {

        nelemToPrint=std::min(nelemToPrint,nelem);
        std::cout <<  "------------------------------------------" << std::endl;
        std::cout <<  "PVCtrlHolder:" << std::endl;
        std::cout <<  "processVariable= "  << pv << std::endl;
        if (strcmp(pvAlias,pv))
        {
            std::cout <<  "pvAlias        = "  << pvAlias << std::endl;
        }
        std::cout <<  "device         = "  << device << std::endl;
        std::cout <<  "attrib         = "  << attrib << std::endl;
        std::cout <<  "dataType       = "  << cafeDataTypeCode.message(dataType).c_str()
                  << " (" << dataType << ") "  << std::endl;
        std::cout <<  "dbrTypeRequest = "  << dbr_type_to_text(dbrDataType)<< std::endl;
        if (dataType!=CAFE_NO_ACCESS || dataType != CAFE_TYPENOTCONN)
        {
            std::cout <<  "nelem          = "  << nelem << std::endl;

            //std::cout <<  "alarmStatus    = "  << alarmStatus << " [" <<  acond.asString(alarmStatus)<< "]" << std::endl;
            //std::cout <<  "alarmSeverity  = "  << alarmSeverity << " [" << aseve.asString(alarmSeverity) << "]"<< std::endl;
            std::cout <<  "alarmStatus    = " << acond.asString(alarmStatus)  << " ("  <<  alarmStatus <<  ")" << std::endl;
            std::cout <<  "alarmSeverity  = " << aseve.asString(alarmSeverity) << " (" <<alarmSeverity <<  ")" << std::endl;


            std::cout <<  "precision      = "  << precision << std::endl;
            std::cout <<  "units          = "  << units << std::endl;
            /*
            if (dataType==CAFE_DOUBLE){
            std::cout <<  "RISC_Pad0      = "  << (dbr_short_t) RISC_pad.d << std::endl
                    // in dbr_ctrl_double this is a RISC_pad
            }
            else if (dataType==CAFE_CHAR) {
            std::cout <<  "RISC_Pad       = "  << (unsigned short) RISC_pad.ch << std::endl;
            }
            else if (dataType==CAFE_FLOAT) {
            std::cout <<  "RISC_Pad       = "  << (dbr_short_t) RISC_pad.f << std::endl;
                   }
                   */
            //if (dataType!=CAFE_ENUM) {
            std::cout <<  "upperDispLimit = "  << getAsString(upperDispLimit)    << std::endl;
            std::cout <<  "lowerDispLimit = "  << getAsString(lowerDispLimit)    << std::endl;
            std::cout <<  "upperAlarmLimit= "  << getAsString(upperAlarmLimit)   << std::endl;
            std::cout <<  "upperWarnLimit = "  << getAsString(upperWarningLimit) << std::endl;
            std::cout <<  "lowerWarnLimit = "  << getAsString(lowerWarningLimit) << std::endl;
            std::cout <<  "lowerAlarmLimit= "  << getAsString(lowerAlarmLimit)   << std::endl;
            if(dbr_type_is_CTRL(dbrDataType))
            {
                std::cout <<  "upperCtrlLimit = "  << getAsString(upperCtrlLimit)  << std::endl;
                std::cout <<  "lowerCtrlLimit = "  << getAsString(lowerCtrlLimit)  << std::endl;
            }
            //}
            //else {
            if (dataType==CAFE_ENUM)
            {
                std::cout <<  "NoStr (ENUM)   = "  << noStr << std::endl;
                std::cout <<  "strs  (ENUM)   = "  ;
                for (short i=0; i< noStr; ++i)
                {
                    std::cout  << "{" << strs[i] << "} " ;
                }
                std::cout <<std::endl;
            }

            std::cout <<  "status         = "  << cafeStatusCode.message(status).c_str() << std::endl;
            if(nelem>0)
            {
                std::cout <<  "value(s)       = "  ;
            }

            switch (dataType)
            {
            case CAFE_STRING:
                for (unsigned int  i=0; i <nelemToPrint; ++i )
                {
                    std::cout << val[i].str << " [" << i << "] " ;
                }
                break;
            case CAFE_SHORT:
                for (unsigned int  i=0; i <nelemToPrint; ++i )
                {
                    std::cout << val[i].s   << " [" << i << "] " ;
                }
                break;
            case CAFE_FLOAT:
                for (unsigned int  i=0; i <nelemToPrint; ++i )
                {
                    std::cout << val[i].f   << " [" << i << "] " ;
                }
                break;
            case CAFE_ENUM:
                for (unsigned int  i=0; i <nelemToPrint; ++i )
                {
                    std::cout <<
                              getAsString(i) << " (" <<  val[i].us << ")"  << " [" << i << "] " ;
                }
                break;
            case CAFE_CHAR:
                for (unsigned int  i=0; i <nelemToPrint; ++i )
                {
                    std::cout << (unsigned short) val[i].ch  << " [" << i << "] " ;
                }
                break;
            case CAFE_LONG:
                for (unsigned int  i=0; i <nelemToPrint; ++i )
                {
                    std::cout << val[i].l   << " [" << i << "] " ;
                }
                break;
            case CAFE_DOUBLE:
                for (unsigned int  i=0; i <nelemToPrint; ++i )
                {
                    std::cout << val[i].d   << " [" << i << "] " ;
                }
                break;
            case CAFE_NO_ACCESS:
            default:
                std::cout << "DATA_TYPE NOT YET DEFINED " << std::endl;
                break;

            }
            std::cout << std::endl;
        }
        std::cout <<  "------------------------------------------" << std::endl;

    };
};

#endif // PVCTRLHOLDER_H
