#!/usr/bin/env python
###############################################################################
# $Id$
#
#  Project:  PROJ
#  Purpose:  Parse XML output of Doxygen on coordinateoperation.hpp to creat
#            C API for projections.
#  Author:   Even Rouault <even.rouault at spatialys.com>
#
###############################################################################
#  Copyright (c) 2018, Even Rouault <even.rouault at spatialys.com>
#
#  Permission is hereby granted, free of charge, to any person obtaining a
#  copy of this software and associated documentation files (the "Software"),
#  to deal in the Software without restriction, including without limitation
#  the rights to use, copy, modify, merge, publish, distribute, sublicense,
#  and/or sell copies of the Software, and to permit persons to whom the
#  Software is furnished to do so, subject to the following conditions:
#
#  The above copyright notice and this permission notice shall be included
#  in all copies or substantial portions of the Software.
#
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
#  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
#  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
#  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
#  DEALINGS IN THE SOFTWARE.
###############################################################################

from lxml import etree
import os

script_dir_name = os.path.dirname(os.path.realpath(__file__))

# Make sure to run doxygen
if not 'SKIP_DOXYGEN' in os.environ:
    os.system("bash " + os.path.join(script_dir_name, "doxygen.sh"))

xmlfilename = os.path.join(os.path.dirname(script_dir_name),
        'docs/build/xml/classosgeo_1_1proj_1_1operation_1_1Conversion.xml')

tree = etree.parse(open(xmlfilename, 'rt'))
root = tree.getroot()
compounddef = root.find('compounddef')

header = open('projections.h', 'wt')
cppfile = open('projections.cpp', 'wt')
test_cppfile = open('test_projections.cpp', 'wt')

header.write("/* BEGIN: Generated by scripts/create_c_api_projections.py*/\n")

cppfile.write("/* BEGIN: Generated by scripts/create_c_api_projections.py*/\n")
cppfile.write("\n");

test_cppfile.write("/* BEGIN: Generated by scripts/create_c_api_projections.py*/\n")

def snake_casify(s):
    out = ''
    lastWasLowerAlpha = False
    for c in s:
        if c.isupper():
            if lastWasLowerAlpha:
                out += '_'
            out += c.lower()
            lastWasLowerAlpha = False
        else:
            out += c
            lastWasLowerAlpha = c.isalpha()
    return out


for sectiondef in compounddef.iter('sectiondef'):
    if sectiondef.attrib['kind'] == 'public-static-func':
        for func in sectiondef.iter('memberdef'):
            name = func.find('name').text
            assert name.startswith('create')
            if name in ('create', 'createChangeVerticalUnit',
                        'createAxisOrderReversal', 'createGeographicGeocentric'):
                continue
            params = []
            has_angle = False
            has_linear = False
            for param in func.iter('param'):
                type = param.find('type').xpath("normalize-space()")
                if type.find('Angle') >= 0:
                    has_angle = True
                if type.find('Length') >= 0:
                    has_linear = True
                paramname = param.find('declname').text
                if paramname == 'properties':
                    continue
                params.append((type, snake_casify(paramname)))

            shortName = name[len('create'):]
            c_shortName = snake_casify(shortName)

            decl = "proj_create_conversion_"
            decl += c_shortName
            decl += "(\n"
            decl += "    PJ_CONTEXT *ctx,\n"
            has_output_params = False
            for param in params:
                if has_output_params:
                    decl += ",\n"

                if param[0] in ('int', 'bool'):
                    decl += "    int " + param[1] 
                else:
                    decl += "    double " + param[1]
                has_output_params = True

            if has_angle:
                if has_output_params:
                    decl += ",\n"
                decl += "    const char* ang_unit_name, double ang_unit_conv_factor"
                has_output_params = True
            if has_linear:
                if has_output_params:
                    decl += ",\n"
                decl += "    const char* linear_unit_name, double linear_unit_conv_factor"
            decl += ")"

            header.write("PJ PROJ_DLL *" + decl + ";\n\n")

            briefdescription = func.find('briefdescription/para').xpath("normalize-space()")
            briefdescription = briefdescription.replace("Instantiate ", "Instantiate a ProjectedCRS with ")

            cppfile.write("// ---------------------------------------------------------------------------\n\n")
            cppfile.write("/** \\brief " + briefdescription + "\n")
            cppfile.write(" *\n")
            cppfile.write(" * See osgeo::proj::operation::Conversion::create" + shortName + "().\n")
            cppfile.write(" *\n")
            cppfile.write(" * Linear parameters are expressed in (linear_unit_name, linear_unit_conv_factor).\n")
            if has_angle:
                cppfile.write(" * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor).\n")
            cppfile.write(" */\n")
            cppfile.write("PJ* " + decl + "{\n");
            cppfile.write("  SANITIZE_CTX(ctx);\n");
            cppfile.write("  try {\n");
            if has_linear:
                cppfile.write("    UnitOfMeasure linearUnit(createLinearUnit(linear_unit_name, linear_unit_conv_factor));\n")
            if has_angle:
                cppfile.write("    UnitOfMeasure angUnit(createAngularUnit(ang_unit_name, ang_unit_conv_factor));\n")
            cppfile.write("    auto conv = Conversion::create" + shortName + "(PropertyMap()")
            for param in params:
                if param[0] in 'int':
                    cppfile.write(", " + param[1])
                elif param[0] in 'bool':
                    cppfile.write(", " + param[1] + " != 0")
                elif param[0].find('Angle') >= 0:
                    cppfile.write(", Angle(" + param[1] + ", angUnit)")
                elif param[0].find('Length') >= 0:
                    cppfile.write(", Length(" + param[1] + ", linearUnit)")
                elif param[0].find('Scale') >= 0:
                    cppfile.write(", Scale(" + param[1] + ")")

            cppfile.write(");\n")
            cppfile.write("    return proj_create_conversion(ctx, conv);\n")
            cppfile.write("  } catch (const std::exception &e) {\n");
            cppfile.write("    proj_log_error(ctx, __FUNCTION__, e.what());\n")
            cppfile.write("  }\n")
            cppfile.write("  return nullptr;\n")
            cppfile.write("}\n")

            test_cppfile.write("{\n")
            test_cppfile.write("    auto projCRS = proj_create_conversion_" + c_shortName + "(\n")
            test_cppfile.write("        m_ctxt")
            if c_shortName == 'utm':
                test_cppfile.write(", 1")
            else:
                for param in params:
                    test_cppfile.write(", 0")
            if has_angle:
                test_cppfile.write(", \"Degree\", 0.0174532925199433")
            if has_linear:
                test_cppfile.write(", \"Metre\", 1.0")
            test_cppfile.write(");\n")
            test_cppfile.write("    ObjectKeeper keeper_projCRS(projCRS);\n")
            test_cppfile.write("    ASSERT_NE(projCRS, nullptr);\n")
            test_cppfile.write("}\n")


header.write("/* END: Generated by scripts/create_c_api_projections.py*/\n")
cppfile.write("/* END: Generated by scripts/create_c_api_projections.py*/\n")

test_cppfile.write("/* END: Generated by scripts/create_c_api_projections.py*/\n")

print('projections.h and .cpp, and test_projections.cpp have been generated. Manually merge them now')
