/*  Copyright (c) MediaArea.net SARL. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license that can
 *  be found in the License.html file in the root of the source tree.
 */

//---------------------------------------------------------------------------
// Pre-compilation
#include "MediaInfo/PreComp.h"
#ifdef __BORLANDC__
    #pragma hdrstop
#endif
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
#include "MediaInfo/Setup.h"
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
#if defined(MEDIAINFO_MIXML_YES)
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
#include "MediaInfo/Multiple/File_MiXml.h"
#include "MediaInfo/MediaInfo_Config.h"
#include "MediaInfo/MediaInfo_Config_MediaInfo.h"
#include "ZenLib/FileName.h"
#include "tinyxml2.h"
using namespace tinyxml2;
//---------------------------------------------------------------------------

namespace MediaInfoLib
{

//***************************************************************************
// Info
//***************************************************************************

//---------------------------------------------------------------------------
extern const char* AC3_Surround[];
extern const char* AC3_Mode[];
extern const char* AC3_Mode_String[];

//---------------------------------------------------------------------------
// What should be hidden by default

struct extra_string
{
    const char** Names;
    const char* ToAdd;
};
struct extra_array
{
    const char** Names;
    const char* Options;
};

static const char* Xml_Extra_HideString[] =
{
    "GuardBand_Before",
    "GuardBand_After",
    "compr",
    "compr_Average",
    "compr_Minimum",
    "compr_Maximum",
    "dialnorm",
    "dialnorm_Average",
    "dialnorm_Minimum",
    "dialnorm_Maximum",
    "dynrng",
    "dynrng_Average",
    "dynrng_Minimum",
    "dynrng_Maximum",
    NULL,
};

static const char* Xml_Extra_String_channel[] =
{
    "BedChannelCount",
    NULL,
};

static const char* Xml_Extra_String_dB[] =
{
    "compr",
    "compr_Average",
    "compr_Minimum",
    "compr_Maximum",
    "dialnorm",
    "dialnorm_Average",
    "dialnorm_Minimum",
    "dialnorm_Maximum",
    "dynrng",
    "dynrng_Average",
    "dynrng_Minimum",
    "dynrng_Maximum",
    NULL,
};

static const char* Xml_Extra_String_micro[] =
{
    "GuardBand_Before",
    "GuardBand_After",
    NULL,
};

static const extra_string Xml_Extra_String[] =
{
    { Xml_Extra_String_channel,         " channel"},
    { Xml_Extra_String_dB,              " dB" },
    { Xml_Extra_String_micro,           " \xC2\xB5s"}, //0xC2 0xB5 = micro sign
    { NULL, NULL},
};

static const char* Xml_Extra_N_NIY[] =
{
    "BedChannelCount",
    "BitRate_Instantaneous",
    "Pos",
    "Index",
    "page_id",
    "region_depth",
    "region_height",
    "region_horizontal_address",
    "region_id",
    "region_vertical_address",
    "region_width",
    "subtitle_stream_id",
    NULL,
};

static const char* Xml_Extra_N_NT[] =
{
    "acmod",
    "bext_Present",
    "BlockAlignment",
    "bsid",
    "CaptionServiceContent_IsPresent",
    "CaptionServiceDescriptor_IsPresent",
    "CaptionServiceName",
    "cdp_length_Max",
    "cdp_length_Min",
    "compr",
    "compr_Average",
    "compr_Count",
    "compr_Maximum",
    "compr_Minimum",
    "data_partitioned",
    "Delay_SDTI",
    "Delay_SDTI",
    "Delay_SystemScheme1",
    "Demux_InitBytes",
    "Density_Unit",
    "Density_X",
    "Density_Y",
    "dialnorm",
    "dialnorm_Average",
    "dialnorm_Count",
    "dialnorm_Maximum",
    "dialnorm_Minimum",
    "dsurmod",
    "dynrng",
    "dynrng_Average",
    "dynrng_Count",
    "dynrng_Maximum",
    "dynrng_Minimum",
    "Errors_Stats",
    "Errors_Stats_03",
    "Errors_Stats_05",
    "Errors_Stats_09",
    "Errors_Stats_10",
    "Errors_Stats_Begin",
    "Errors_Stats_End",
    "Errors_Stats_End_03",
    "Errors_Stats_End_05",
    "FrameCount_Speed",
    "GuardBand_After",
    "GuardBand_Before",
    "IBI",
    "intra_dc_precision",
    "lfeon",
    "OverallBitRate_Precision_Max",
    "OverallBitRate_Precision_Min",
    "PCR_Distance_Average",
    "PCR_Distance_Max",
    "PCR_Distance_Min",
    "PCR_Invalid_Count",
    "PrimaryPackage",
    "reversible_vlc",
    "SideCar_FilePos",
    "Source_Delay",
    "Source_Delay_Source",
    "Source_List",
    "UniversalAdID_Registry",
    "UniversalAdID_Value",
    "UnsupportedSources",
    NULL,
};

static const extra_array Xml_Extra_Array[] =
{
    { Xml_Extra_N_NIY,                  "N NIY" },
    { Xml_Extra_N_NT,                   "N NT" },
    { NULL, NULL },
};

namespace 
{
    struct nested
{
    XMLElement* Target;
    string Name;
};
}

//***************************************************************************
// Constructor/Destructor
//***************************************************************************

//---------------------------------------------------------------------------
File_MiXml::File_MiXml()
:File__Analyze()
{
    #if MEDIAINFO_EVENTS
        ParserIDs[0]=MediaInfo_Parser_MiXml;
    #endif //MEDIAINFO_EVENTS
}

//***************************************************************************
// Buffer - File header
//***************************************************************************

//---------------------------------------------------------------------------
bool File_MiXml::FileHeader_Begin()
{
    XMLDocument document;
    if (!FileHeader_Begin_XML(document))
       return false;

    {
        XMLElement* Root=document.FirstChildElement("MediaInfo");
        if (Root)
        {
            const char* Attribute = Root->Attribute("xmlns");
            if (Attribute == NULL || Ztring().From_UTF8(Attribute) != __T("https://mediaarea.net/mediainfo"))
            {
                Reject("MiXml");
                return false;
            }

            Accept("MiXml");

            XMLElement* Media = Root->FirstChildElement();
            while (Media)
            {
                if (string(Media->Value()) == "media")
                {
                    Attribute = Media->Attribute("ref");
                    if (Attribute)
                    {
                        File_Name.From_UTF8(Attribute);
                        Config->File_Names.clear();
                        
                        Fill(Stream_General, 0, General_CompleteName, File_Name, true); //TODO: merge with generic code
                        Fill(Stream_General, 0, General_FolderName, FileName::Path_Get(File_Name), true);
                        Fill(Stream_General, 0, General_FileName, FileName::Name_Get(File_Name), true);
                        Fill(Stream_General, 0, General_FileExtension, FileName::Extension_Get(File_Name), true);
                        if (Retrieve(Stream_General, 0, General_FileExtension).empty())
                            Fill(Stream_General, 0, General_FileNameExtension, Retrieve(Stream_General, 0, General_FileName), true);
                        else
                            Fill(Stream_General, 0, General_FileNameExtension, Retrieve(Stream_General, 0, General_FileName)+__T('.')+Retrieve(Stream_General, 0, General_FileExtension), true);
                    }

                    XMLElement* Track = Media->FirstChildElement();
                    while (Track)
                    {
                        if (string(Track->Value()) == "track")
                        {
                            Attribute = Track->Attribute("type");
                            if (Attribute)
                            {
                                string StreamKind(Attribute);

                                StreamKind_Last = Stream_Max;
                                if (StreamKind == "General")
                                    StreamKind_Last = Stream_General;
                                if (StreamKind == "Video")
                                    Stream_Prepare(Stream_Video);
                                if (StreamKind == "Audio")
                                    Stream_Prepare(Stream_Audio);
                                if (StreamKind == "Text")
                                    Stream_Prepare(Stream_Text);
                                if (StreamKind == "Other")
                                    Stream_Prepare(Stream_Other);
                                if (StreamKind == "Image")
                                    Stream_Prepare(Stream_Image);
                                if (StreamKind == "Menu")
                                    Stream_Prepare(Stream_Menu);

                                if (StreamKind_Last != Stream_Max)
                                {
                                    XMLElement* Element = Track->FirstChildElement();
                                    while (Element)
                                    {
                                        string Name(Element->Name());
                                             if (Name == "Format_Version")
                                            Fill(StreamKind_Last, StreamPos_Last, Element->Name(), string("Version ")+Element->GetText(), true, true);
                                        else if (MediaInfoLib::Config.Info_Get(StreamKind_Last).Read(Ztring().From_UTF8(Element->Name()), Info_Measure) == __T(" ms"))
                                        {
                                            //Converting seconds to milliseconds while keeping precision
                                            Ztring N;
                                            N.From_UTF8(Element->GetText());
                                            size_t Dot = N.find('.');
                                            size_t Precision = 0;
                                            if (Dot != string::npos)
                                            {
                                                size_t End = N.find_first_not_of(__T("0123456789"), Dot + 1);
                                                if (End == string::npos)
                                                    End = N.size();
                                                Precision = End - (Dot + 1);
                                                if (Precision <= 3)
                                                    Precision = 0;
                                                else
                                                    Precision -= 3;
                                            }
                                            
                                            Fill(StreamKind_Last, StreamPos_Last, Element->Name(), N.To_float64()*1000, Precision, true);
                                        }
                                        else if (Name != "extra")
                                        {
                                            //Special cases
                                            if (Name == "Channels")
                                                Fill(StreamKind_Last, StreamPos_Last, "Channel(s)", Element->GetText());
                                            //Generic filling
                                            else
                                                Fill(StreamKind_Last, StreamPos_Last, Element->Name(), Element->GetText(), Unlimited, true, true);
                                        }
                                        else
                                        {
                                            XMLElement* Extra = Element->FirstChildElement();
                                            map<string, size_t> Nested_Pos;
                                            vector<nested> Nested;
                                            Nested.resize(1);
                                            nested* LastNested = &Nested[0];
                                            LastNested->Target = Extra;

                                            while (Extra)
                                            {
                                                // Nested elements
                                                if (!Extra->GetText())
                                                {
                                                    LastNested->Target = Extra;
                                                    Nested.resize(Nested.size() + 1);
                                                    LastNested = &Nested[Nested.size() - 1];
                                                    LastNested->Name = Extra->Name();
                                                    Extra = Extra->FirstChildElement();
                                                    if (Nested.size() >= 2)
                                                        LastNested->Name.insert(0, Nested[Nested.size() - 2].Name);
                                                    if (Extra && !strcmp(Extra->Name(), "Pos") && Extra->GetText())
                                                        LastNested->Name += Extra->GetText();
                                                    Fill(StreamKind_Last, StreamPos_Last, LastNested->Name.c_str(), "Yes", Unlimited, true, true);
                                                    LastNested->Name += ' ';
                                                    LastNested->Target = NULL;
                                                    Nested_Pos[LastNested->Name]++;
                                                    continue;
                                                }

                                                if (strstr(Extra->Name(), "_String"))
                                                {
                                                    Extra = Extra->NextSiblingElement();
                                                    continue;
                                                }

                                                Fill(StreamKind_Last, StreamPos_Last, (LastNested->Name + Extra->Name()).c_str(), Extra->GetText(), Unlimited, true, true);

                                                bool HasString=false;
                                                for (size_t i=0; Xml_Extra_String[i].Names; i++)
                                                    for (size_t j=0; Xml_Extra_String[i].Names[j]; j++)
                                                        if (!strcmp(Extra->Name(), Xml_Extra_String[i].Names[j]))
                                                        {
                                                            Fill(StreamKind_Last, StreamPos_Last, (LastNested->Name + Extra->Name() + "/String").c_str(), MediaInfoLib::Config.Language_Get(Extra->GetText(), Ztring().From_UTF8(Xml_Extra_String[i].ToAdd)), true);
                                                            HasString=true;
                                                            break;
                                                        }

                                                for (size_t i=0; Xml_Extra_Array[i].Names; i++)
                                                    for (size_t j=0; Xml_Extra_Array[i].Names[j]; j++)
                                                        if (!strcmp(Extra->Name(), Xml_Extra_Array[i].Names[j]))
                                                        {
                                                            Fill_SetOptions(StreamKind_Last, StreamPos_Last, (LastNested->Name + Extra->Name()).c_str(), Xml_Extra_Array[i].Options);
                                                            if (HasString)
                                                            {
                                                                string Options(Xml_Extra_Array[i].Options);
                                                                if (InfoOption_ShowInXml<=Options.size())
                                                                    Options.resize(InfoOption_ShowInXml+1, ' ');
                                                                char Show='Y';
                                                                for (size_t k=0; Xml_Extra_HideString[k]; k++)
                                                                    if (!strcmp(Extra->Name(), Xml_Extra_HideString[k]))
                                                                    {
                                                                        Show='N';
                                                                        break;
                                                                    }
                                                                Options[InfoOption_ShowInInform]=Show;
                                                                Options[InfoOption_ShowInXml]='N';
                                                                Fill_SetOptions(StreamKind_Last, StreamPos_Last, (LastNested->Name + Extra->Name() + "/String").c_str(), Options.c_str());
                                                            }
                                                            break;
                                                        }

                                                if (!strcmp(Extra->Name(), "dsurmod"))
                                                {
                                                    size_t dsurmod=Ztring().From_UTF8(Extra->GetText()).To_int32u();
                                                    if (dsurmod<4)
                                                    {
                                                        Fill(StreamKind_Last, StreamPos_Last, (LastNested->Name + "dsurmod/String").c_str(), AC3_Surround[dsurmod]);
                                                        Fill_SetOptions(StreamKind_Last, StreamPos_Last, (LastNested->Name + "dsurmod/String").c_str(), "N NTN");
                                                    }
                                                }

                                                if (!strcmp(Extra->Name(), "ServiceKind"))
                                                {
                                                    const char* ServiceKind=Extra->GetText();
                                                    for (int8u i=0; i<8; i++)
                                                        if (!strcmp(ServiceKind, AC3_Mode[i]))
                                                        {
                                                            Fill(Stream_Audio, 0, (LastNested->Name + "ServiceKind/String").c_str(), AC3_Mode_String[i]);
                                                            break;
                                                        }
                                                }

                                                if (!strncmp(Extra->Name(), "LinkedTo_", 9))
                                                {
                                                    string Name=Extra->Name();
                                                    if (Name.find("_Pos", Name.size() - 4) == Name.size() - 4)
                                                    {
                                                        ZtringList List;
                                                        List.Separator_Set(0, __T(" + "));
                                                        List.Write(Ztring().From_UTF8(Extra->GetText()));
                                                        for (size_t i = 0; i < List.size(); i++)
                                                            if (!List[i].empty() && List[i].find_first_not_of(__T("0123456789")) == string::npos)
                                                                List[i].From_Number(List[i].To_int64u() + 1);
                                                        Fill_SetOptions(StreamKind_Last, StreamPos_Last, (LastNested->Name + Extra->Name()).c_str(), "N NTY");
                                                        Fill(StreamKind_Last, StreamPos_Last, (LastNested->Name + Extra->Name() + "/String").c_str(), List.Read());
                                                        Fill_SetOptions(StreamKind_Last, StreamPos_Last, (LastNested->Name + Extra->Name() + "/String").c_str(), "Y NTN");
                                                    }
                                                }

                                                Extra = Extra->NextSiblingElement();
                                                if (!Extra && !Nested.empty())
                                                {
                                                    Nested.pop_back();
                                                    if (!Nested.empty())
                                                    {
                                                        LastNested = &Nested[Nested.size() - 1];
                                                        Extra = LastNested->Target->NextSiblingElement();
                                                    }
                                                }
                                            }
                                        }

                                        // Extra filling (duplicated content) //TODO: better handling of all such fields
                                             if (Name == "Format_Settings_Endianness")
                                            Fill(StreamKind_Last, StreamPos_Last, "Format_Settings", Element->GetText());
                                        else if (Name == "Format_Settings_Packing")
                                            Fill(StreamKind_Last, StreamPos_Last, "Format_Settings", Element->GetText());

                                        Element = Element->NextSiblingElement();
                                    }
                                }
                            }
                        }

                        Track = Track->NextSiblingElement();
                    }
                }

                Media = Media->NextSiblingElement();
            }
        }
        else
        {
            Reject("MiXml");
            return false;
        }
    }

    //Special cases
    Ztring UniversalAdID_Value=Retrieve(Stream_General, 0, General_UniversalAdID_Value);
    Ztring UniversalAdID_Registry=Retrieve(Stream_General, 0, General_UniversalAdID_Registry);
    if (!UniversalAdID_Value.empty() && !UniversalAdID_Registry.empty())
    {
        Fill(Stream_General, 0, General_UniversalAdID_String, UniversalAdID_Value + __T(" (") + UniversalAdID_Registry + __T(")"), true);
    }
    Ztring MajorBrand=Retrieve(Stream_General, 0, General_CodecID);
    if (!UniversalAdID_Value.empty() && !UniversalAdID_Registry.empty())
    {
        Ztring CodecID_String=MajorBrand;
        Ztring Compatible=Retrieve(Stream_General, 0, General_CodecID);
        if (MajorBrand == __T("qt  "))
        {
            CodecID_String += __T(' ');
            CodecID_String += Retrieve(Stream_General, 0, General_CodecID_Version);
        }
        if (!Compatible.empty())
        {
            CodecID_String += __T(" (");
            CodecID_String += Compatible;
            CodecID_String += __T(')');
        }
        Fill(Stream_General, 0, General_CodecID_String, CodecID_String, true);
    }

    Element_Offset=File_Size;

    //All should be OK...
    return true;
}

} //NameSpace

#endif //MEDIAINFO_MiXml_YES

