//*********************************************************************************
//                               Rendition.cc
//---------------------------------------------------------------------------------
//
//---------------------------------------------------------------------------------
// Hugo Mercier <hmercier31[at]gmail.com> (c) 2008
// Pino Toscano <pino@kde.org> (c) 2008
// Carlos Garcia Campos <carlosgc@gnome.org> (c) 2010
// Tobias Koenig <tobias.koenig@kdab.com> (c) 2012
// Albert Astals Cid <aacid@kde.org> (C) 2017, 2018, 2024
//
// 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 2 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, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//*********************************************************************************

#include <cmath>
#include "Rendition.h"
#include "FileSpec.h"

MediaWindowParameters::MediaWindowParameters()
{
    // default values
    type = windowEmbedded;
    width = -1;
    height = -1;
    relativeTo = windowRelativeToDocument;
    XPosition = 0.5;
    YPosition = 0.5;
    hasTitleBar = true;
    hasCloseButton = true;
    isResizeable = true;
}

MediaWindowParameters::~MediaWindowParameters() { }

void MediaWindowParameters::parseFWParams(Object *obj)
{
    Object tmp = obj->dictLookup("D");
    if (tmp.isArray()) {
        Array *dim = tmp.getArray();

        if (dim->getLength() >= 2) {
            Object dd = dim->get(0);
            if (dd.isInt()) {
                width = dd.getInt();
            }

            dd = dim->get(1);
            if (dd.isInt()) {
                height = dd.getInt();
            }
        }
    }

    tmp = obj->dictLookup("RT");
    if (tmp.isInt()) {
        int t = tmp.getInt();
        switch (t) {
        case 0:
            relativeTo = windowRelativeToDocument;
            break;
        case 1:
            relativeTo = windowRelativeToApplication;
            break;
        case 2:
            relativeTo = windowRelativeToDesktop;
            break;
        }
    }

    tmp = obj->dictLookup("P");
    if (tmp.isInt()) {
        int t = tmp.getInt();

        switch (t) {
        case 0: // Upper left
            XPosition = 0.0;
            YPosition = 0.0;
            break;
        case 1: // Upper Center
            XPosition = 0.5;
            YPosition = 0.0;
            break;
        case 2: // Upper Right
            XPosition = 1.0;
            YPosition = 0.0;
            break;
        case 3: // Center Left
            XPosition = 0.0;
            YPosition = 0.5;
            break;
        case 4: // Center
            XPosition = 0.5;
            YPosition = 0.5;
            break;
        case 5: // Center Right
            XPosition = 1.0;
            YPosition = 0.5;
            break;
        case 6: // Lower Left
            XPosition = 0.0;
            YPosition = 1.0;
            break;
        case 7: // Lower Center
            XPosition = 0.5;
            YPosition = 1.0;
            break;
        case 8: // Lower Right
            XPosition = 1.0;
            YPosition = 1.0;
            break;
        }
    }

    tmp = obj->dictLookup("T");
    if (tmp.isBool()) {
        hasTitleBar = tmp.getBool();
    }
    tmp = obj->dictLookup("UC");
    if (tmp.isBool()) {
        hasCloseButton = tmp.getBool();
    }
    tmp = obj->dictLookup("R");
    if (tmp.isInt()) {
        isResizeable = (tmp.getInt() != 0);
    }
}

MediaParameters::MediaParameters()
{
    // instanciate to default values

    volume = 100;
    fittingPolicy = fittingUndefined;
    autoPlay = true;
    repeatCount = 1.0;
    opacity = 1.0;
    showControls = false;
    duration = 0;
}

MediaParameters::~MediaParameters() { }

void MediaParameters::parseMediaPlayParameters(Object *obj)
{
    Object tmp = obj->dictLookup("V");
    if (tmp.isInt()) {
        volume = tmp.getInt();
    }

    tmp = obj->dictLookup("C");
    if (tmp.isBool()) {
        showControls = tmp.getBool();
    }

    tmp = obj->dictLookup("F");
    if (tmp.isInt()) {
        int t = tmp.getInt();

        switch (t) {
        case 0:
            fittingPolicy = fittingMeet;
            break;
        case 1:
            fittingPolicy = fittingSlice;
            break;
        case 2:
            fittingPolicy = fittingFill;
            break;
        case 3:
            fittingPolicy = fittingScroll;
            break;
        case 4:
            fittingPolicy = fittingHidden;
            break;
        case 5:
            fittingPolicy = fittingUndefined;
            break;
        }
    }

    // duration parsing
    // duration's default value is set to 0, which means : intrinsinc media duration
    tmp = obj->dictLookup("D");
    if (tmp.isDict()) {
        Object oname = tmp.dictLookup("S");
        if (oname.isName()) {
            const char *name = oname.getName();
            if (!strcmp(name, "F")) {
                duration = -1; // infinity
            } else if (!strcmp(name, "T")) {
                Object ddict = tmp.dictLookup("T");
                if (ddict.isDict()) {
                    Object tmp2 = ddict.dictLookup("V");
                    if (tmp2.isNum()) {
                        duration = (unsigned long)(tmp2.getNum());
                    }
                }
            }
        }
    }

    tmp = obj->dictLookup("A");
    if (tmp.isBool()) {
        autoPlay = tmp.getBool();
    }

    tmp = obj->dictLookup("RC");
    if (tmp.isNum()) {
        repeatCount = tmp.getNum();
    }
}

void MediaParameters::parseMediaScreenParameters(Object *obj)
{
    Object tmp = obj->dictLookup("W");
    if (tmp.isInt()) {
        int t = tmp.getInt();

        switch (t) {
        case 0:
            windowParams.type = MediaWindowParameters::windowFloating;
            break;
        case 1:
            windowParams.type = MediaWindowParameters::windowFullscreen;
            break;
        case 2:
            windowParams.type = MediaWindowParameters::windowHidden;
            break;
        case 3:
            windowParams.type = MediaWindowParameters::windowEmbedded;
            break;
        }
    }

    // background color
    tmp = obj->dictLookup("B");
    if (tmp.isArray()) {
        Array *color = tmp.getArray();

        Object component = color->get(0);
        bgColor.r = component.getNum();

        component = color->get(1);
        bgColor.g = component.getNum();

        component = color->get(2);
        bgColor.b = component.getNum();
    }

    // opacity
    tmp = obj->dictLookup("O");
    if (tmp.isNum()) {
        opacity = tmp.getNum();
    }

    if (windowParams.type == MediaWindowParameters::windowFloating) {
        Object winDict = obj->dictLookup("F");
        if (winDict.isDict()) {
            windowParams.parseFWParams(&winDict);
        }
    }
}

MediaRendition::~MediaRendition()
{
    delete fileName;
    delete contentType;
}

MediaRendition::MediaRendition(Object *obj)
{
    bool hasClip = false;

    ok = true;
    fileName = nullptr;
    contentType = nullptr;
    isEmbedded = false;

    //
    // Parse media clip data
    //
    Object tmp2 = obj->dictLookup("C");
    if (tmp2.isDict()) { // media clip
        hasClip = true;
        Object tmp = tmp2.dictLookup("S");
        if (tmp.isName()) {
            if (!strcmp(tmp.getName(), "MCD")) { // media clip data
                Object obj1 = tmp2.dictLookup("D");
                if (obj1.isDict()) {
                    Object obj2 = obj1.dictLookup("F");
                    if (obj2.isString()) {
                        fileName = obj2.getString()->copy();
                    }
                    obj2 = obj1.dictLookup("EF");
                    if (obj2.isDict()) {
                        Object embedded = obj2.dictLookup("F");
                        if (embedded.isStream()) {
                            isEmbedded = true;
                            embeddedStreamObject = embedded.copy();
                        }
                    }

                    // TODO: D might be a form XObject too
                } else {
                    error(errSyntaxError, -1, "Invalid Media Clip Data");
                    ok = false;
                }

                // FIXME: ignore CT if D is a form XObject
                obj1 = tmp2.dictLookup("CT");
                if (obj1.isString()) {
                    contentType = obj1.getString()->copy();
                }
            } else if (!strcmp(tmp.getName(), "MCS")) { // media clip data
                // TODO
            }
        } else {
            error(errSyntaxError, -1, "Invalid Media Clip");
            ok = false;
        }
    }

    if (!ok) {
        return;
    }

    //
    // parse Media Play Parameters
    tmp2 = obj->dictLookup("P");
    if (tmp2.isDict()) { // media play parameters
        Object params = tmp2.dictLookup("MH");
        if (params.isDict()) {
            MH.parseMediaPlayParameters(&params);
        }
        params = tmp2.dictLookup("BE");
        if (params.isDict()) {
            BE.parseMediaPlayParameters(&params);
        }
    } else if (!hasClip) {
        error(errSyntaxError, -1, "Invalid Media Rendition");
        ok = false;
    }

    //
    // parse Media Screen Parameters
    tmp2 = obj->dictLookup("SP");
    if (tmp2.isDict()) { // media screen parameters
        Object params = tmp2.dictLookup("MH");
        if (params.isDict()) {
            MH.parseMediaScreenParameters(&params);
        }
        params = tmp2.dictLookup("BE");
        if (params.isDict()) {
            BE.parseMediaScreenParameters(&params);
        }
    }
}

MediaRendition::MediaRendition(const MediaRendition &other)
{
    ok = other.ok;
    MH = other.MH;
    BE = other.BE;
    isEmbedded = other.isEmbedded;
    embeddedStreamObject = other.embeddedStreamObject.copy();

    if (other.contentType) {
        contentType = other.contentType->copy();
    } else {
        contentType = nullptr;
    }

    if (other.fileName) {
        fileName = other.fileName->copy();
    } else {
        fileName = nullptr;
    }
}

void MediaRendition::outputToFile(FILE *fp)
{
    if (!isEmbedded) {
        return;
    }

    embeddedStreamObject.streamReset();

    while (true) {
        int c = embeddedStreamObject.streamGetChar();
        if (c == EOF) {
            break;
        }

        fwrite(&c, 1, 1, fp);
    }
}

std::unique_ptr<MediaRendition> MediaRendition::copy() const
{
    return std::make_unique<MediaRendition>(*this);
}

// TODO: SelectorRendition
