/*
 * Copyright (C) 2015-2021 Intel Corporation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

#include <errno.h>
#include <expat.h>
#include <fcntl.h>
#include <linux/media.h>
#include <linux/v4l2-subdev.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include <string>
#include <vector>

#ifdef CAL_BUILD
#include <cros-camera/v4l2_device.h>
#else
#include <v4l2_device.h>
#endif

#include "CameraTypes.h"
#include "NodeInfo.h"
#include "iutils/Thread.h"

namespace icamera {

struct MediaEntity;
struct MediaPad;
struct MediaLink;

#define MEDIA_CTL_DEV_NAME "/dev/media"
#define MEDIA_DRIVER_NAME "intel-ipu"
#define MEDIA_DEVICE_MAX_NUM 256

enum {
    FC_FORMAT = 0,
    FC_SELECTION = 1,
};

enum ResolutionType {
    RESOLUTION_MAX = 0,
    RESOLUTION_COMPOSE,
    RESOLUTION_CROP,
    RESOLUTION_TARGET,

};

struct McFormat {
    int entity;
    int pad;
    int stream;
    int formatType;
    int selCmd;
    int top;
    int left;
    int width;
    int height;
    enum ResolutionType type;
    std::string entityName;
    unsigned int pixelCode;
    McFormat() {
        entity = 0;
        pad = 0;
        stream = 0;
        formatType = 0;
        selCmd = 0;
        top = 0;
        left = 0;
        width = 0;
        height = 0;
        type = RESOLUTION_MAX;
        pixelCode = 0;
    }
};

struct McOutput {
    Port port;
    unsigned int v4l2Format;
    int width;
    int height;
    McOutput() {
        port = INVALID_PORT;
        v4l2Format = 0;
        width = 0;
        height = 0;
    }
};

struct McCtl {
    int entity;
    int ctlCmd;
    int ctlValue;
    std::string ctlName;
    std::string entityName;
    McCtl() {
        entity = 0;
        ctlCmd = 0;
        ctlValue = 0;
    }
};

struct McLink {
    int srcEntity;
    int srcPad;
    int sinkEntity;
    int sinkPad;
    bool enable;
    std::string srcEntityName;
    std::string sinkEntityName;
    McLink() {
        srcEntity = 0;
        srcPad = 0;
        sinkEntity = 0;
        sinkPad = 0;
        enable = false;
    }
};

struct McRoute {
    int entity;
    uint32_t sinkPad;
    uint32_t sinkStream;
    uint32_t srcPad;
    uint32_t srcStream;
    uint32_t flag;
    std::string entityName;
    McRoute() {
        entity = 0;
        sinkPad = 0;
        srcPad = 0;
        sinkStream = 0;
        srcStream = 0;
        flag = 0;
        entityName.clear();
    }
};

struct McVideoNode {
    std::string name;
    VideoNodeType videoNodeType;
    McVideoNode() { videoNodeType = VIDEO_GENERIC; }
};

struct MediaCtlConf {
    std::vector<McCtl> ctls;
    std::vector<McLink> links;
    std::vector<McRoute> routes;
    std::vector<McFormat> formats;
    std::vector<McOutput> outputs;
    std::vector<McVideoNode> videoNodes;
    int mcId;
    int outputWidth;
    int outputHeight;
    std::vector<ConfigMode> configMode;
    int format;
    /*
     * The outputWidth or outputHeight is 0 if there isn't this setting
     * in MediaCtlConf. It means the isys output size is dynamic, and
     * we don't use stream size to select MC.
     */
    MediaCtlConf() {
        mcId = -1;
        outputWidth = 0;
        outputHeight = 0;
        format = -1;
    }
};

/**
 * \class MediaController
 *
 * This class is used for discovering and configuring the internal topology
 * of a media device. Devices are modelled as an oriented graph of building
 * blocks called media entities. The media entities are connected to each other
 * through pads.
 *
 * Each media entity corresponds to a V4L2 subdevice. This class is also used
 * for configuring the V4L2 subdevices.
 */

class MediaControl {
 public:
    /**
     * \brief Get the singleton instance of MediaControl
     */
    static MediaControl* getInstance();

    /**
     * \brief Release the singleton instance of MediaControl.
     */
    static void releaseInstance();

    /**
     * \brief Enum entities and link, and reset all links
     *
     * \return 0 if succeed, other value indicates failed
     */
    int initEntities();

    /**
     * \brief Free all entities and links memory
     */
    void clearEntities();

    /**
     * \brief Get the entity by name
     *
     * \return entity id if succeed or -1 if error
     */
    int getEntityIdByName(const char* name);

    /**
     * \brief Get VCM I2C bus address
     *
     * \return 0 if succeed, other value indicates failed
     */
    int getVCMI2CAddr(const char* vcmName, std::string* vcmI2CAddr);

    /**
     * \brief Set up media controller pipe
     *
     * \param cameraId: the current camera id
     * \param mc: the MediaCtlConf got from platform data
     * \param sensorName: the sensor name get from platform data
     * \param width: The width of the request frame
     * \param height: The height of the request frame
     * \param format: The format of the request frame
     * \param field: The field of the request frame
     *
     * \return OK if succeed, other value indicates failed
     */
    int mediaCtlSetup(int cameraId, MediaCtlConf* mc, int width, int height, int field);

    /**
     * \brief Clear media controller pipe
     *
     *
     * \param cameraId: the current camera id
     * \param mc: the MediaCtlConf got from platform data
     */
    void mediaCtlClear(int cameraId, MediaCtlConf* mc);

    int resetAllLinks();

    int getLensName(std::string* lensName);
    bool checkAvailableSensor(const std::string& sensorEntityName,
                              const std::string& sinkEntityName);
    /**
     * Getting I2C bus address by the name of sensor entity and the name of sensor's sink entity.
     *
     * \sensorEntityName: the name of sensor entity.
     * \sinkEntityName: the name of sensor's sink entity.
     * \i2cBus: I2C bus address.
     * \return OK if succeed, other value indicates failed
     */
    int getI2CBusAddress(const std::string& sensorEntityName, const std::string& sinkEntityName,
                         std::string* i2cBus);

 private:
    MediaControl& operator=(const MediaControl&);
    MediaControl(const char* devName);
    ~MediaControl();

    static MediaControl* getMediaControlInstance();
    int openDevice();
    void closeDevice(int fd);

    // enum MediaControl info.
    int enumInfo();
    int enumLinks(int fd);
    int enumEntities(int fd, media_device_info& devInfo);

    // get entity info.
    int getDevnameFromSysfs(MediaEntity* entity);
    MediaEntity* getEntityById(uint32_t id);
    MediaEntity* getEntityByName(const char* name);

    // set up entity link.

    MediaLink* entityAddLink(MediaEntity* entity);
    int setupLink(uint32_t srcEntity, uint32_t srcPad, uint32_t sinkEntity, uint32_t sinkPad,
                  bool enable);
    int setupLink(MediaPad* source, MediaPad* sink, uint32_t flags);

    // set up MediaCtlConf info.
    void setMediaMcCtl(int cameraId, std::vector<McCtl> ctls);
    int setMediaMcLink(std::vector<McLink> links);
    int setFormat(int cameraId, const McFormat* format, int targetWidth, int targetHeight,
                  int field);
    int setSelection(int cameraId, const McFormat* format, int targetWidth, int targetHeight);

    /* Dump functions */
    void dumpInfo(media_device_info& devInfo);
    void dumpEntityDesc(media_entity_desc& desc, media_device_info& devInfo);
    void dumpPadDesc(media_pad_desc* pads, const int padsCount = 1, const char* name = nullptr);
    void dumpLinkDesc(media_link_desc* links, const int linksCount = 1);
    const char* entitySubtype2String(unsigned type);
    const char* padType2String(unsigned flag);
    // DUMP_ENTITY_TOPOLOGY_S
    void dumpEntityTopology(bool dot = true);
    void dumpTopologyDot();
    void dumpTopologyText();
    // DUMP_ENTITY_TOPOLOGY_E
    void setSensorOrientation(int cameraId);

    std::string mDevName;
    std::vector<MediaEntity> mEntities;

    static MediaControl* sInstance;
    static Mutex sLock;
};

}  // namespace icamera
