// CreateCoder.cpp

#include "StdAfx.h"

#include "../../Windows/Defs.h"
#include "../../Windows/PropVariant.h"

#include "CreateCoder.h"

#include "FilterCoder.h"
#include "RegisterCodec.h"

static const unsigned kNumCodecsMax = 64;
extern
unsigned g_NumCodecs;
unsigned g_NumCodecs = 0;
extern
const CCodecInfo *g_Codecs[];
const CCodecInfo *g_Codecs[kNumCodecsMax];

// We use g_ExternalCodecs in other stages.
#ifdef Z7_EXTERNAL_CODECS
/*
extern CExternalCodecs g_ExternalCodecs;
#define CHECK_GLOBAL_CODECS \
    if (!_externalCodecs || !_externalCodecs->IsSet()) _externalCodecs = &g_ExternalCodecs;
*/
#define CHECK_GLOBAL_CODECS
#endif


void RegisterCodec(const CCodecInfo *codecInfo) throw()
{
  if (g_NumCodecs < kNumCodecsMax)
    g_Codecs[g_NumCodecs++] = codecInfo;
}

static const unsigned kNumHashersMax = 16;
extern
unsigned g_NumHashers;
unsigned g_NumHashers = 0;
extern
const CHasherInfo *g_Hashers[];
const CHasherInfo *g_Hashers[kNumHashersMax];

void RegisterHasher(const CHasherInfo *hashInfo) throw()
{
  if (g_NumHashers < kNumHashersMax)
    g_Hashers[g_NumHashers++] = hashInfo;
}


#ifdef Z7_EXTERNAL_CODECS

static HRESULT ReadNumberOfStreams(ICompressCodecsInfo *codecsInfo, UInt32 index, PROPID propID, UInt32 &res)
{
  NWindows::NCOM::CPropVariant prop;
  RINOK(codecsInfo->GetProperty(index, propID, &prop))
  if (prop.vt == VT_EMPTY)
    res = 1;
  else if (prop.vt == VT_UI4)
    res = prop.ulVal;
  else
    return E_INVALIDARG;
  return S_OK;
}

static HRESULT ReadIsAssignedProp(ICompressCodecsInfo *codecsInfo, UInt32 index, PROPID propID, bool &res)
{
  NWindows::NCOM::CPropVariant prop;
  RINOK(codecsInfo->GetProperty(index, propID, &prop))
  if (prop.vt == VT_EMPTY)
    res = true;
  else if (prop.vt == VT_BOOL)
    res = VARIANT_BOOLToBool(prop.boolVal);
  else
    return E_INVALIDARG;
  return S_OK;
}

HRESULT CExternalCodecs::Load()
{
  Codecs.Clear();
  Hashers.Clear();

  if (GetCodecs)
  {
    CCodecInfoEx info;
    
    UString s;
    UInt32 num;
    RINOK(GetCodecs->GetNumMethods(&num))
    
    for (UInt32 i = 0; i < num; i++)
    {
      NWindows::NCOM::CPropVariant prop;
      
      RINOK(GetCodecs->GetProperty(i, NMethodPropID::kID, &prop))
      if (prop.vt != VT_UI8)
        continue; // old Interface
      info.Id = prop.uhVal.QuadPart;
      
      prop.Clear();
      
      info.Name.Empty();
      RINOK(GetCodecs->GetProperty(i, NMethodPropID::kName, &prop))
      if (prop.vt == VT_BSTR)
        info.Name.SetFromWStr_if_Ascii(prop.bstrVal);
      else if (prop.vt != VT_EMPTY)
        continue;
      
      RINOK(ReadNumberOfStreams(GetCodecs, i, NMethodPropID::kPackStreams, info.NumStreams))
      {
        UInt32 numUnpackStreams = 1;
        RINOK(ReadNumberOfStreams(GetCodecs, i, NMethodPropID::kUnpackStreams, numUnpackStreams))
        if (numUnpackStreams != 1)
          continue;
      }
      RINOK(ReadIsAssignedProp(GetCodecs, i, NMethodPropID::kEncoderIsAssigned, info.EncoderIsAssigned))
      RINOK(ReadIsAssignedProp(GetCodecs, i, NMethodPropID::kDecoderIsAssigned, info.DecoderIsAssigned))
      RINOK(ReadIsAssignedProp(GetCodecs, i, NMethodPropID::kIsFilter, info.IsFilter))
      
      Codecs.Add(info);
    }
  }
  
  if (GetHashers)
  {
    UInt32 num = GetHashers->GetNumHashers();
    CHasherInfoEx info;
    
    for (UInt32 i = 0; i < num; i++)
    {
      NWindows::NCOM::CPropVariant prop;

      RINOK(GetHashers->GetHasherProp(i, NMethodPropID::kID, &prop))
      if (prop.vt != VT_UI8)
        continue;
      info.Id = prop.uhVal.QuadPart;
      
      prop.Clear();
      
      info.Name.Empty();
      RINOK(GetHashers->GetHasherProp(i, NMethodPropID::kName, &prop))
      if (prop.vt == VT_BSTR)
        info.Name.SetFromWStr_if_Ascii(prop.bstrVal);
      else if (prop.vt != VT_EMPTY)
        continue;
      
      Hashers.Add(info);
    }
  }
  
  return S_OK;
}

#endif


int FindMethod_Index(
    DECL_EXTERNAL_CODECS_LOC_VARS
    const AString &name,
    bool encode,
    CMethodId &methodId,
    UInt32 &numStreams,
    bool &isFilter)
{
  unsigned i;
  for (i = 0; i < g_NumCodecs; i++)
  {
    const CCodecInfo &codec = *g_Codecs[i];
    if ((encode ? codec.CreateEncoder : codec.CreateDecoder)
        && StringsAreEqualNoCase_Ascii(name, codec.Name))
    {
      methodId = codec.Id;
      numStreams = codec.NumStreams;
      isFilter = codec.IsFilter;
      return (int)i;
    }
  }
  
  #ifdef Z7_EXTERNAL_CODECS
  
  CHECK_GLOBAL_CODECS

  if (_externalCodecs)
    for (i = 0; i < _externalCodecs->Codecs.Size(); i++)
    {
      const CCodecInfoEx &codec = _externalCodecs->Codecs[i];
      if ((encode ? codec.EncoderIsAssigned : codec.DecoderIsAssigned)
          && StringsAreEqualNoCase_Ascii(name, codec.Name))
      {
        methodId = codec.Id;
        numStreams = codec.NumStreams;
        isFilter = codec.IsFilter;
        return (int)(g_NumCodecs + i);
      }
    }
  
  #endif
  
  return -1;
}


static int FindMethod_Index(
    DECL_EXTERNAL_CODECS_LOC_VARS
    CMethodId methodId, bool encode)
{
  unsigned i;
  for (i = 0; i < g_NumCodecs; i++)
  {
    const CCodecInfo &codec = *g_Codecs[i];
    if (codec.Id == methodId && (encode ? codec.CreateEncoder : codec.CreateDecoder))
      return (int)i;
  }
  
  #ifdef Z7_EXTERNAL_CODECS
  
  CHECK_GLOBAL_CODECS

  if (_externalCodecs)
    for (i = 0; i < _externalCodecs->Codecs.Size(); i++)
    {
      const CCodecInfoEx &codec = _externalCodecs->Codecs[i];
      if (codec.Id == methodId && (encode ? codec.EncoderIsAssigned : codec.DecoderIsAssigned))
        return (int)(g_NumCodecs + i);
    }
  
  #endif
  
  return -1;
}


bool FindMethod(
    DECL_EXTERNAL_CODECS_LOC_VARS
    CMethodId methodId,
    AString &name)
{
  name.Empty();
 
  unsigned i;
  for (i = 0; i < g_NumCodecs; i++)
  {
    const CCodecInfo &codec = *g_Codecs[i];
    if (methodId == codec.Id)
    {
      name = codec.Name;
      return true;
    }
  }
  
  #ifdef Z7_EXTERNAL_CODECS

  CHECK_GLOBAL_CODECS

  if (_externalCodecs)
    for (i = 0; i < _externalCodecs->Codecs.Size(); i++)
    {
      const CCodecInfoEx &codec = _externalCodecs->Codecs[i];
      if (methodId == codec.Id)
      {
        name = codec.Name;
        return true;
      }
    }
  
  #endif
  
  return false;
}

bool FindHashMethod(
    DECL_EXTERNAL_CODECS_LOC_VARS
    const AString &name,
    CMethodId &methodId)
{
  unsigned i;
  for (i = 0; i < g_NumHashers; i++)
  {
    const CHasherInfo &codec = *g_Hashers[i];
    if (StringsAreEqualNoCase_Ascii(name, codec.Name))
    {
      methodId = codec.Id;
      return true;
    }
  }
  
  #ifdef Z7_EXTERNAL_CODECS

  CHECK_GLOBAL_CODECS

  if (_externalCodecs)
    for (i = 0; i < _externalCodecs->Hashers.Size(); i++)
    {
      const CHasherInfoEx &codec = _externalCodecs->Hashers[i];
      if (StringsAreEqualNoCase_Ascii(name, codec.Name))
      {
        methodId = codec.Id;
        return true;
      }
    }
  
  #endif
  
  return false;
}

void GetHashMethods(
    DECL_EXTERNAL_CODECS_LOC_VARS
    CRecordVector<CMethodId> &methods)
{
  methods.ClearAndSetSize(g_NumHashers);
  unsigned i;
  for (i = 0; i < g_NumHashers; i++)
    methods[i] = (*g_Hashers[i]).Id;
  
  #ifdef Z7_EXTERNAL_CODECS
  
  CHECK_GLOBAL_CODECS

  if (_externalCodecs)
    for (i = 0; i < _externalCodecs->Hashers.Size(); i++)
      methods.Add(_externalCodecs->Hashers[i].Id);
  
  #endif
}



HRESULT CreateCoder_Index(
    DECL_EXTERNAL_CODECS_LOC_VARS
    unsigned i, bool encode,
    CMyComPtr<ICompressFilter> &filter,
    CCreatedCoder &cod)
{
  cod.IsExternal = false;
  cod.IsFilter = false;
  cod.NumStreams = 1;

  if (i < g_NumCodecs)
  {
    const CCodecInfo &codec = *g_Codecs[i];
    // if (codec.Id == methodId)
    {
      if (encode)
      {
        if (codec.CreateEncoder)
        {
          void *p = codec.CreateEncoder();
          if (codec.IsFilter) filter = (ICompressFilter *)p;
          else if (codec.NumStreams == 1) cod.Coder = (ICompressCoder *)p;
          else { cod.Coder2 = (ICompressCoder2 *)p; cod.NumStreams = codec.NumStreams; }
          return S_OK;
        }
      }
      else
        if (codec.CreateDecoder)
        {
          void *p = codec.CreateDecoder();
          if (codec.IsFilter) filter = (ICompressFilter *)p;
          else if (codec.NumStreams == 1) cod.Coder = (ICompressCoder *)p;
          else { cod.Coder2 = (ICompressCoder2 *)p; cod.NumStreams = codec.NumStreams; }
          return S_OK;
        }
    }
  }

  #ifdef Z7_EXTERNAL_CODECS

  CHECK_GLOBAL_CODECS
  
  if (_externalCodecs)
  {
    i -= g_NumCodecs;
    cod.IsExternal = true;
    if (i < _externalCodecs->Codecs.Size())
    {
      const CCodecInfoEx &codec = _externalCodecs->Codecs[i];
      // if (codec.Id == methodId)
      {
        if (encode)
        {
          if (codec.EncoderIsAssigned)
          {
            if (codec.NumStreams == 1)
            {
              const HRESULT res = _externalCodecs->GetCodecs->CreateEncoder(i, &IID_ICompressCoder, (void **)&cod.Coder);
              if (res != S_OK && res != E_NOINTERFACE && res != CLASS_E_CLASSNOTAVAILABLE)
                return res;
              if (cod.Coder)
                return res;
              return _externalCodecs->GetCodecs->CreateEncoder(i, &IID_ICompressFilter, (void **)&filter);
            }
            cod.NumStreams = codec.NumStreams;
            return _externalCodecs->GetCodecs->CreateEncoder(i, &IID_ICompressCoder2, (void **)&cod.Coder2);
          }
        }
        else
          if (codec.DecoderIsAssigned)
          {
            if (codec.NumStreams == 1)
            {
              const HRESULT res = _externalCodecs->GetCodecs->CreateDecoder(i, &IID_ICompressCoder, (void **)&cod.Coder);
              if (res != S_OK && res != E_NOINTERFACE && res != CLASS_E_CLASSNOTAVAILABLE)
                return res;
              if (cod.Coder)
                return res;
              return _externalCodecs->GetCodecs->CreateDecoder(i, &IID_ICompressFilter, (void **)&filter);
            }
            cod.NumStreams = codec.NumStreams;
            return _externalCodecs->GetCodecs->CreateDecoder(i, &IID_ICompressCoder2, (void **)&cod.Coder2);
          }
      }
    }
  }
  #endif

  return S_OK;
}


HRESULT CreateCoder_Index(
    DECL_EXTERNAL_CODECS_LOC_VARS
    unsigned index, bool encode,
    CCreatedCoder &cod)
{
  CMyComPtr<ICompressFilter> filter;
  const HRESULT res = CreateCoder_Index(
      EXTERNAL_CODECS_LOC_VARS
      index, encode,
      filter, cod);
  
  if (filter)
  {
    cod.IsFilter = true;
    CFilterCoder *coderSpec = new CFilterCoder(encode);
    cod.Coder = coderSpec;
    coderSpec->Filter = filter;
  }
  
  return res;
}


HRESULT CreateCoder_Id(
    DECL_EXTERNAL_CODECS_LOC_VARS
    CMethodId methodId, bool encode,
    CMyComPtr<ICompressFilter> &filter,
    CCreatedCoder &cod)
{
  const int index = FindMethod_Index(EXTERNAL_CODECS_LOC_VARS methodId, encode);
  if (index < 0)
    return S_OK;
  return CreateCoder_Index(EXTERNAL_CODECS_LOC_VARS (unsigned)index, encode, filter, cod);
}


HRESULT CreateCoder_Id(
    DECL_EXTERNAL_CODECS_LOC_VARS
    CMethodId methodId, bool encode,
    CCreatedCoder &cod)
{
  CMyComPtr<ICompressFilter> filter;
  const HRESULT res = CreateCoder_Id(
      EXTERNAL_CODECS_LOC_VARS
      methodId, encode,
      filter, cod);
  
  if (filter)
  {
    cod.IsFilter = true;
    CFilterCoder *coderSpec = new CFilterCoder(encode);
    cod.Coder = coderSpec;
    coderSpec->Filter = filter;
  }
  
  return res;
}


HRESULT CreateCoder_Id(
    DECL_EXTERNAL_CODECS_LOC_VARS
    CMethodId methodId, bool encode,
    CMyComPtr<ICompressCoder> &coder)
{
  CCreatedCoder cod;
  const HRESULT res = CreateCoder_Id(
      EXTERNAL_CODECS_LOC_VARS
      methodId, encode,
      cod);
  coder = cod.Coder;
  return res;
}

HRESULT CreateFilter(
    DECL_EXTERNAL_CODECS_LOC_VARS
    CMethodId methodId, bool encode,
    CMyComPtr<ICompressFilter> &filter)
{
  CCreatedCoder cod;
  return CreateCoder_Id(
      EXTERNAL_CODECS_LOC_VARS
      methodId, encode,
      filter, cod);
}


HRESULT CreateHasher(
    DECL_EXTERNAL_CODECS_LOC_VARS
    CMethodId methodId,
    AString &name,
    CMyComPtr<IHasher> &hasher)
{
  name.Empty();

  unsigned i;
  for (i = 0; i < g_NumHashers; i++)
  {
    const CHasherInfo &codec = *g_Hashers[i];
    if (codec.Id == methodId)
    {
      hasher = codec.CreateHasher();
      name = codec.Name;
      break;
    }
  }

  #ifdef Z7_EXTERNAL_CODECS

  CHECK_GLOBAL_CODECS

  if (!hasher && _externalCodecs)
    for (i = 0; i < _externalCodecs->Hashers.Size(); i++)
    {
      const CHasherInfoEx &codec = _externalCodecs->Hashers[i];
      if (codec.Id == methodId)
      {
        name = codec.Name;
        return _externalCodecs->GetHashers->CreateHasher((UInt32)i, &hasher);
      }
    }

  #endif

  return S_OK;
}
