// This file is part of the AliceVision project.
// Copyright (c) 2015 AliceVision contributors.
// Copyright (c) 2012 openMVG contributors.
// This Source Code Form is subject to the terms of the Mozilla Public License,
// v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

#pragma once

#include "aliceVision/numeric/numeric.hpp"

#include <boost/algorithm/string.hpp>

#include <fstream>
#include <iterator>
#include <set>
#include <sstream>
#include <string>
#include <vector>
#include <algorithm>  // std::lexicographical_compare
#include <cctype>     // std::tolower

namespace aliceVision {
namespace SfMIO {

struct CameraInfo
{
    std::string m_sImageName;
    size_t m_intrinsicId;
};

struct IntrinsicCameraInfo
{
    size_t m_w, m_h;
    float m_focal;
    Mat3 m_K;
    double m_k1;
    double m_k2;
    double m_k3;
    bool m_bKnownIntrinsic;  // true if 11 or 6, else false
    std::string m_sCameraMaker, m_sCameraModel;

    IntrinsicCameraInfo()
      : m_w(0),
        m_h(0),
        m_K(Mat3::Zero()),
        m_k1(0),
        m_k2(0),
        m_k3(0),
        m_bKnownIntrinsic(false),
        m_sCameraModel(""),
        m_sCameraMaker("")
    {}

    /// Functor used to tell if two IntrinsicCameraInfo share the same optical properties
    friend bool operator==(IntrinsicCameraInfo const& ci1, IntrinsicCameraInfo const& ci2)
    {
        // Two camera share optical properties if they share the same K matrix (and the same camera name)
        bool bequal = ci1.m_K == ci2.m_K && ci1.m_sCameraMaker == ci2.m_sCameraMaker && ci1.m_sCameraModel == ci2.m_sCameraModel;
        return bequal;
    }
};

// Load an image file list
// One basename per line.
// It handle different scenario based on the intrinsic info of the tested image
// - a camera without exif data
// - a camera with exif data found in the database
// - a camera with exif data not found in the database
// - a camera with known intrinsic
inline bool loadImageList(std::vector<CameraInfo>& vec_camImageName,
                          std::vector<IntrinsicCameraInfo>& vec_focalGroup,
                          const std::string& sFileName,
                          bool bVerbose = true)
{
    std::ifstream in(sFileName);
    if (!in.is_open())
    {
        std::cerr << std::endl << "Impossible to read the specified file." << std::endl;
    }
    std::string sValue;
    std::vector<std::string> vec_str;
    while (getline(in, sValue))
    {
        vec_str.clear();
        boost::trim(sValue);
        boost::split(vec_str, sValue, boost::is_any_of(";"));
        if (vec_str.size() == 1)
        {
            std::cerr << "Invalid input file" << std::endl;
            in.close();
            return false;
        }
        std::stringstream oss;
        oss.clear();
        oss.str(vec_str[1]);
        size_t width, height;
        oss >> width;
        oss.clear();
        oss.str(vec_str[2]);
        oss >> height;

        IntrinsicCameraInfo intrinsicCamInfo;
        intrinsicCamInfo.m_w = width;
        intrinsicCamInfo.m_h = height;

        switch (vec_str.size())
        {
            case 3:  // a camera without exif data
            {
                intrinsicCamInfo.m_focal = -1;
                intrinsicCamInfo.m_bKnownIntrinsic = false;
                intrinsicCamInfo.m_sCameraMaker = "";
                intrinsicCamInfo.m_sCameraModel = "";
            }
            break;
            case 5:  // a camera with exif data not found in the database
            {
                intrinsicCamInfo.m_focal = -1;
                intrinsicCamInfo.m_bKnownIntrinsic = false;
                intrinsicCamInfo.m_sCameraMaker = vec_str[3];
                intrinsicCamInfo.m_sCameraModel = vec_str[4];
            }
            break;
            case 6:  // a camera with exif data found in the database
            {
                oss.clear();
                oss.str(vec_str[3]);
                float focal;
                oss >> focal;
                intrinsicCamInfo.m_focal = focal;
                intrinsicCamInfo.m_bKnownIntrinsic = true;
                intrinsicCamInfo.m_sCameraMaker = vec_str[4];
                intrinsicCamInfo.m_sCameraModel = vec_str[5];

                Mat3 K;
                K << focal, 0, float(width) / 2.f, 0, focal, float(height) / 2.f, 0, 0, 1;
                intrinsicCamInfo.m_K = K;
            }
            break;
            case 12:  // a camera with known intrinsic
            {
                intrinsicCamInfo.m_bKnownIntrinsic = true;
                intrinsicCamInfo.m_sCameraMaker = intrinsicCamInfo.m_sCameraModel = "";

                Mat3 K = Mat3::Identity();

                oss.clear();
                oss.str(vec_str[3]);
                oss >> K(0, 0);
                oss.clear();
                oss.str(vec_str[4]);
                oss >> K(0, 1);
                oss.clear();
                oss.str(vec_str[5]);
                oss >> K(0, 2);
                oss.clear();
                oss.str(vec_str[6]);
                oss >> K(1, 0);
                oss.clear();
                oss.str(vec_str[7]);
                oss >> K(1, 1);
                oss.clear();
                oss.str(vec_str[8]);
                oss >> K(1, 2);
                oss.clear();
                oss.str(vec_str[9]);
                oss >> K(2, 0);
                oss.clear();
                oss.str(vec_str[10]);
                oss >> K(2, 1);
                oss.clear();
                oss.str(vec_str[11]);
                oss >> K(2, 2);

                intrinsicCamInfo.m_K = K;
                intrinsicCamInfo.m_focal = static_cast<float>(K(0, 0));  // unknown sensor size;
            }
            break;
            default:
            {
                std::cerr << "Invalid image list line: wrong number of arguments" << std::endl;
                in.close();
                return false;
            }
        }

        std::vector<IntrinsicCameraInfo>::const_iterator iterIntrinsicGroup = find(vec_focalGroup.begin(), vec_focalGroup.end(), intrinsicCamInfo);
        size_t id = -1;
        if (iterIntrinsicGroup == vec_focalGroup.end())
        {
            vec_focalGroup.push_back(intrinsicCamInfo);
            id = vec_focalGroup.size() - 1;
        }
        else
        {
            id = std::distance(std::vector<IntrinsicCameraInfo>::const_iterator(vec_focalGroup.begin()), iterIntrinsicGroup);
        }

        CameraInfo camInfo;
        camInfo.m_sImageName = vec_str[0];
        camInfo.m_intrinsicId = id;
        vec_camImageName.push_back(camInfo);

        vec_str.clear();
    }
    in.close();
    return !(vec_camImageName.empty());
}

//-- Load an image list file but only return camera image names
inline bool loadImageList(std::vector<std::string>& vec_camImageName, const std::string& sListFileName, bool bVerbose = true)
{
    vec_camImageName.clear();
    std::vector<aliceVision::SfMIO::CameraInfo> vec_camImageIntrinsicInfo;
    std::vector<aliceVision::SfMIO::IntrinsicCameraInfo> vec_focalGroup;
    if (loadImageList(vec_camImageIntrinsicInfo, vec_focalGroup, sListFileName, bVerbose))
    {
        for (std::vector<aliceVision::SfMIO::CameraInfo>::const_iterator iter_camInfo = vec_camImageIntrinsicInfo.begin();
             iter_camInfo != vec_camImageIntrinsicInfo.end();
             iter_camInfo++)
        {
            vec_camImageName.push_back(iter_camInfo->m_sImageName);
        }
    }
    return (!vec_camImageName.empty());
}

}  // namespace SfMIO
}  // namespace aliceVision
