/*
 * ConfigPlist.h
 *
 *  Created on: Oct 9, 2020
 *      Author: jief
 */

#ifndef _CONFIGPLISTCLASS_GRAPHICS_H_
#define _CONFIGPLISTCLASS_GRAPHICS_H_


class Graphics_Class : public XmlDict
{
  using super = XmlDict;
public:
  const ConfigPlistClass& configPlist;

    class Graphics_PatchVBiosBytes_Class : public XmlDict
    {
      protected:
        XmlData Find = XmlData();
        XmlData Replace = XmlData();

        XmlDictField m_fields[2] = {
          {"Find", Find},
          {"Replace", Replace},
        };

        virtual void getFields(XmlDictField** fields, size_t* nb) override { *fields = m_fields; *nb = sizeof(m_fields)/sizeof(m_fields[0]); };

      public:
        const decltype(Find)::ValueType& dgetFind() const { return Find.isDefined() ? Find.value() : Find.nullValue; };
        const decltype(Replace)::ValueType& dgetReplace() const { return Replace.isDefined() ? Replace.value() : Replace.nullValue; };

    };


    class Graphics_ATI_NVIDIA_Class : public XmlDict
    {
      using super = XmlDict;
      protected:
        XmlString8AllowEmpty Model = XmlString8AllowEmpty();
        XmlUInt32 IOPCIPrimaryMatch = XmlUInt32(); // Id
        XmlUInt32 IOPCISubDevId = XmlUInt32();     // SubId
        XmlUInt64 VRAM = XmlUInt64();              // VideoRam
        XmlUInt64 VideoPorts = decltype(VideoPorts)();        // VideoPorts
        XmlBool LoadVBios = XmlBool();             // LoadVBios

        XmlDictField m_fields[6] = {
          {"Model", Model},
          {"IOPCIPrimaryMatch", IOPCIPrimaryMatch},
          {"IOPCISubDevId", IOPCISubDevId},
          {"VRAM", VRAM},
          {"VideoPorts", VideoPorts},
          {"LoadVBios", LoadVBios},
        };

        virtual void getFields(XmlDictField** fields, size_t* nb) override { *fields = m_fields; *nb = sizeof(m_fields)/sizeof(m_fields[0]); };
      public:
        const decltype(Model)::ValueType& dgetModel() const { return Model.isDefined() ? Model.value() : Model.nullValue; };
        const decltype(IOPCIPrimaryMatch)::ValueType& dgetId() const { return IOPCIPrimaryMatch.isDefined() ? IOPCIPrimaryMatch.value() : IOPCIPrimaryMatch.nullValue; };
        const decltype(IOPCISubDevId)::ValueType& dgetSubId() const { return IOPCISubDevId.isDefined() ? IOPCISubDevId.value() : IOPCISubDevId.nullValue; };
        decltype(VRAM)::ValueType dgetVideoRam() const { return VRAM.isDefined() ? LShiftU64(VRAM.value(), 20) : VRAM.nullValue; }; //Mb -> bytes
        const decltype(VideoPorts)::ValueType& dgetVideoPorts() const { return VideoPorts.isDefined() ? VideoPorts.value() : VideoPorts.nullValue; };
        const decltype(LoadVBios)::ValueType& dgetLoadVBios() const { return LoadVBios.isDefined() ? LoadVBios.value() : LoadVBios.nullValue; };
        UINT32 dgetSignature() const { return SIGNATURE_32('C','A','R','D'); };

    };

    class Graphics_EDID_Class : public XmlDict
    {
      using super = XmlDict;
    public:
      XmlBool Inject = XmlBool();
      XmlData Custom = XmlData();
      XmlUInt16 VendorID = XmlUInt16();
      XmlUInt16 ProductID = XmlUInt16();
      XmlUInt16 HorizontalSyncPulseWidth = XmlUInt16();
      XmlUInt8 VideoInputSignal = XmlUInt8();

      XmlDictField m_fields[6] = {
        {"Inject", Inject},
        {"Custom", Custom},
        {"VendorID", VendorID},
        {"ProductID", ProductID},
        {"HorizontalSyncPulseWidth", HorizontalSyncPulseWidth},
        {"VideoInputSignal", VideoInputSignal},
      };

      virtual void getFields(XmlDictField** fields, size_t* nb) override { *fields = m_fields; *nb = sizeof(m_fields)/sizeof(m_fields[0]); };
      virtual XBool validate(XmlLiteParser* xmlLiteParser, const XString8& xmlPath, const XmlParserPosition& keyPos, XBool generateErrors) override {
        RETURN_IF_FALSE( super::validate(xmlLiteParser, xmlPath, keyPos, generateErrors) );
        if ( Inject.isDefined() ) {
          if ( Inject.value() ) {
            if ( !Custom.isDefined() ) {
// Each patch of VID/PID/HSPW/VIS can work independently without custom values.
//              xmlLiteParser->addWarning(generateErrors, S8Printf("Custom has to be defined if Inject is defined in dict '%s:%d'.", xmlPath.c_str(), keyPos.getLine()));
//              return false; // Allow this to be compatible with old way of reading settings. Can be put back when definitive switch is made
            }else{
              if ( Custom.value().size() % 128 != 0 ) {
                xmlLiteParser->addWarning(generateErrors, S8Printf("Custom length must be modulo 128 in dict '%s:%d'.", xmlPath.c_str(), keyPos.getLine()));
                Custom.reset();
                return true; // that's what Clover is currently doing.
              }
            }
          }else{
            if ( Custom.isDefined() ) {
              xmlLiteParser->addWarning(generateErrors, S8Printf("Custom is defined and will be ignored because Inject is false in dict '%s:%d'.", xmlPath.c_str(), keyPos.getLine()));
              return false;
            }
          }
        }else{
          if ( Custom.isDefined() ) {
            xmlLiteParser->addWarning(generateErrors, S8Printf("Custom is defined and will be ignored because Inject isn't defined '%s:%d'.", xmlPath.c_str(), keyPos.getLine()));
            return false;
          }
        }
        return true;
      
      };
      const decltype(Inject)::ValueType& dgetInjectEDID() const { return Inject.isDefined() ? Inject.value() : Inject.nullValue; };
      const decltype(Custom)::ValueType& dgetCustomEDID() const { return dgetInjectEDID() && Custom.isDefined() ? Custom.value() : Custom.nullValue; };
      const decltype(VendorID)::ValueType& dgetVendorEDID() const { return dgetInjectEDID() && VendorID.isDefined() ? VendorID.value() : VendorID.nullValue; };
      const decltype(ProductID)::ValueType& dgetProductEDID() const { return dgetInjectEDID() && ProductID.isDefined() ? ProductID.value() : ProductID.nullValue; };
      const decltype(HorizontalSyncPulseWidth)::ValueType& dgetEdidFixHorizontalSyncPulseWidth() const { return dgetInjectEDID() && HorizontalSyncPulseWidth.isDefined() ? HorizontalSyncPulseWidth.value() : HorizontalSyncPulseWidth.nullValue; };
      const decltype(VideoInputSignal)::ValueType& dgetEdidFixVideoInputSignal() const { return dgetInjectEDID() && VideoInputSignal.isDefined() ? VideoInputSignal.value() : VideoInputSignal.nullValue; };

    };
    class XmlInjectUnion;
    class Graphics_Inject_Class : public XmlDict
    {
      using super = XmlDict;
    protected:
      XmlBool Intel = XmlBool();
      XmlBool ATI = XmlBool();
      XmlBool NVidia = XmlBool();

      XmlDictField m_fields[3] = {
        {"Intel", Intel},
        {"ATI", ATI},
        {"NVidia", NVidia},
      };

      virtual void getFields(XmlDictField** fields, size_t* nb) override { *fields = m_fields; *nb = sizeof(m_fields)/sizeof(m_fields[0]); };
    protected:
      const decltype(Intel)::ValueType& dgetIntel() const { return Intel.isDefined() ? Intel.value() : Intel.nullValue; };
      const decltype(ATI)::ValueType& dgetATI() const { return ATI.isDefined() ? ATI.value() : ATI.nullValue; };
      const decltype(NVidia)::ValueType& dgetNVidia() const { return NVidia.isDefined() ? NVidia.value() : NVidia.nullValue; };
      friend class XmlInjectUnion;
      XBool isInjectIntelDefined() const { return Intel.isDefined(); };
      XBool isInjectATIDefined() const { return ATI.isDefined(); };
      XBool isInjectNVidiaDefined() const { return NVidia.isDefined(); };
    };

    class XmlInjectUnion: public XmlUnion
    {
      protected:
        XmlBool xmlBool = XmlBool();
        Graphics_Inject_Class xmlDict = Graphics_Inject_Class();
        XmlUnionField m_fields[2] = { xmlBool, xmlDict};
        virtual void getFields(XmlUnionField** fields, size_t* nb) override { *fields = m_fields; *nb = sizeof(m_fields)/sizeof(m_fields[0]); };
      public:
        XBool dgetGraphicsInjector() const {
          if ( xmlBool.isDefined() ) return xmlBool.value();
          return false;
        }
      const decltype(Graphics_Inject_Class::Intel)::ValueType& dgetInjectIntel() const { return xmlBool.isDefined() ? xmlBool.value() : xmlDict.isDefined() ? xmlDict.dgetIntel() : decltype(Graphics_Inject_Class::Intel)::nullValue; };
      const decltype(Graphics_Inject_Class::ATI)::ValueType& dgetInjectATI() const { return xmlBool.isDefined() ? xmlBool.value() : xmlDict.isDefined() ? xmlDict.dgetATI() : decltype(Graphics_Inject_Class::ATI)::nullValue; };
      const decltype(Graphics_Inject_Class::NVidia)::ValueType& dgetInjectNVidia() const { return xmlBool.isDefined() ? xmlBool.value() : xmlDict.isDefined() ? xmlDict.dgetNVidia() : decltype(Graphics_Inject_Class::NVidia)::nullValue; };

      XBool isInjectIntelDefined() const { return xmlBool.isDefined() || xmlDict.isInjectIntelDefined(); };
      XBool isInjectATIDefined() const { return xmlBool.isDefined() || xmlDict.isInjectATIDefined(); };
      XBool isInjectNVidiaDefined() const { return xmlBool.isDefined() || xmlDict.isInjectNVidiaDefined(); };
    };
    
protected:
  XmlBool PatchVBios = XmlBool();
  XmlBool RadeonDeInit = XmlBool();
  XmlUInt64 VRAM = XmlUInt64();
  XmlUInt32 RefCLK = XmlUInt32();
  XmlBool LoadVBios = XmlBool();
  XmlUInt16 VideoPorts = XmlUInt16();
  XmlInt8 BootDisplay = XmlInt8();
  XmlString8AllowEmpty FBName = XmlString8AllowEmpty();
  XmlString8AllowEmpty NVCAP = XmlString8AllowEmpty(); // It's currently an hex sequence without 0x. TODO validation. Only hex char, space and comma (see hex2bin), length < 20
  XmlString8AllowEmpty displayCfg = XmlString8AllowEmpty(); // It's currently an hex sequence without 0x. TODO validation. Only hex char, space and comma, length < 8
  XmlUInt32 DualLink = XmlUInt32();
  XmlBool NvidiaGeneric = XmlBool();
  XmlBool NvidiaNoEFI = XmlBool();
  XmlBool NvidiaSingle = XmlBool();
  XmlUInt32 igPlatformId = XmlUInt32();
  XmlUInt32 snbPlatformId = XmlUInt32();

public:
  XmlInjectUnion Inject = XmlInjectUnion();
  Graphics_EDID_Class EDID = Graphics_EDID_Class();
  XmlArray<Graphics_PatchVBiosBytes_Class> PatchVBiosBytesArray = XmlArray<Graphics_PatchVBiosBytes_Class>();
  XmlArray<Graphics_ATI_NVIDIA_Class> ATI = XmlArray<Graphics_ATI_NVIDIA_Class>();
  XmlArray<Graphics_ATI_NVIDIA_Class> NVIDIA = XmlArray<Graphics_ATI_NVIDIA_Class>();

  XmlDictField m_fields[21] = {
    {"PatchVBios", PatchVBios},
    {"RadeonDeInit", RadeonDeInit},
    {"VRAM", VRAM},
    {"RefCLK", RefCLK},
    {"LoadVBios", LoadVBios},
    {"VideoPorts", VideoPorts},
    {"BootDisplay", BootDisplay},
    {"FBName", FBName},
    {"NVCAP", NVCAP},
    {"display-cfg", displayCfg},
    {"DualLink", DualLink},
    {"NvidiaGeneric", NvidiaGeneric},
    {"NvidiaNoEFI", NvidiaNoEFI},
    {"NvidiaSingle", NvidiaSingle},
    {"ig-platform-id", igPlatformId},
    {"snb-platform-id", snbPlatformId}, // TODO: validate : shpouldn't have both.
    {"Inject", Inject},
    {"EDID", EDID},
    {"PatchVBiosBytes", PatchVBiosBytesArray},
    {"ATI", ATI},
    {"NVIDIA", NVIDIA},
  };

  Graphics_Class(const ConfigPlistClass& _configPlist) : configPlist(_configPlist) {}

  virtual void getFields(XmlDictField** fields, size_t* nb) override { *fields = m_fields; *nb = sizeof(m_fields)/sizeof(m_fields[0]); };

  const decltype(PatchVBios)::ValueType& dgetPatchVBios() const { return PatchVBios.isDefined() ? PatchVBios.value() : PatchVBios.nullValue; };
  const decltype(PatchVBiosBytesArray)::ValueType& dgetPatchVBiosBytes() const { return PatchVBiosBytesArray.isDefined() ? PatchVBiosBytesArray.value() : PatchVBiosBytesArray.nullValue; };
  const decltype(RadeonDeInit)::ValueType& dgetRadeonDeInit() const { return RadeonDeInit.isDefined() ? RadeonDeInit.value() : RadeonDeInit.nullValue; };
  const decltype(VRAM)::ValueType& dgetVRAM() const { return VRAM.isDefined() ? VRAM.value() : VRAM.nullValue; };
  const decltype(RefCLK)::ValueType& dgetRefCLK() const { return RefCLK.isDefined() ? RefCLK.value() : RefCLK.nullValue; };
  const decltype(LoadVBios)::ValueType& dgetLoadVBios() const { return LoadVBios.isDefined() ? LoadVBios.value() : LoadVBios.nullValue; };
  const decltype(VideoPorts)::ValueType& dgetVideoPorts() const { return VideoPorts.isDefined() ? VideoPorts.value() : VideoPorts.nullValue; };
  int8_t dgetBootDisplay() const { return isDefined() ? BootDisplay.isDefined() ? BootDisplay.value() : -1 : 0; }; // TODO: different default value if section is not defined
  const decltype(FBName)::ValueType& dgetFBName() const { return FBName.isDefined() ? FBName.value() : FBName.nullValue; };
//  const decltype(displayCfg)::ValueType& dgetdisplayCfg() const { return displayCfg.isDefined() ? displayCfg.value() : displayCfg.nullValue; };
  decltype(DualLink)::ValueType dgetDualLink() const { return DualLink.isDefined() ? DualLink.value() : 0xA; };
  const decltype(NvidiaGeneric)::ValueType& dgetNvidiaGeneric() const { return NvidiaGeneric.isDefined() ? NvidiaGeneric.value() : NvidiaGeneric.nullValue; };
  const decltype(NvidiaNoEFI)::ValueType& dgetNvidiaNoEFI() const { return NvidiaNoEFI.isDefined() ? NvidiaNoEFI.value() : NvidiaNoEFI.nullValue; };
  const decltype(NvidiaSingle)::ValueType& dgetNvidiaSingle() const { return NvidiaSingle.isDefined() ? NvidiaSingle.value() : NvidiaSingle.nullValue; };
  decltype(igPlatformId)::ValueType dget_IgPlatform() const {
    for ( size_t idx = 0 ; idx < configPlist.Devices.Arbitrary.size() ; ++idx ) {
      const ConfigPlistClass::DevicesClass::Devices_Arbitrary_Class& arbitraryProperty = configPlist.Devices.Arbitrary[idx];
      for ( size_t jdx = 0 ; jdx < arbitraryProperty.CustomProperties.size() ; ++jdx ) {
        const ConfigPlistClass::DevicesClass::SimplePropertyClass_Class& customProperty = arbitraryProperty.CustomProperties[jdx];
        if ( customProperty.key.isDefined() && customProperty.key.value().contains("-platform-id") ) {
          XBuffer<uint8_t> buf = customProperty.dgetValue();
          if ( buf.size() == 0 ) return 0;
          decltype(igPlatformId)::ValueType v = 0;
          memcpy(&v, buf.data(), MIN(sizeof(decltype(igPlatformId)::ValueType), buf.size()));
          return v;
        }
      }
    }
    if ( snbPlatformId.isDefined() ) return snbPlatformId.value();
    return igPlatformId.isDefined() ? igPlatformId.value() : igPlatformId.nullValue;
  };
//  const decltype(snbPlatformId)::ValueType& dgetsnbPlatformId() const { return snbPlatformId.isDefined() ? snbPlatformId.value() : snbPlatformId.nullValue; };
  
//  undefinable_bool dgetInjectAsBool() const { return Inject.isDefined() && Inject.xmlBool.isDefined() ? undefinable_bool(Inject.xmlBool.value()) : undefinable_bool(); };
  const XArray<uint8_t> dgetDcfg() const {
    XArray<uint8_t> xbuffer;
    xbuffer.Add(0, 8);
    if ( displayCfg.isDefined() ) {
      hex2bin(displayCfg.value().c_str(), displayCfg.value().length(), xbuffer.data(), 8);
    }else{
      memcpy(xbuffer.data(), default_dcfg_0, 4);
      memcpy(&xbuffer[4], default_dcfg_1, 4);
    }
    return xbuffer;
  };
  const XArray<uint8_t> dgetNVCAP() const {
    XArray<uint8_t> xbuffer;
    xbuffer.Add(0, 20);
    if ( NVCAP.isDefined() ) {
      hex2bin(NVCAP.value().c_str(), NVCAP.value().length(), xbuffer.data(), 20);
    }else{
      memcpy(xbuffer.data(), default_NVCAP, 20);
    }
    return xbuffer;
  };

  const decltype(NVIDIA)::ValueType& dgetNVIDIA() const { return NVIDIA.isDefined() ? NVIDIA.value() : NVIDIA.nullValue; };

};


#endif /* _CONFIGPLISTCLASS_GRAPHICS_H_ */
