'''
Copyright (c) 2017-2020 VMware, Inc.  All rights reserved.

builder.py

Build script for generating vSAN C# SDK Dynamic-link libraries (DLLs).

builder.py is a Python script written for building the vSAN C# SDK DLLs. This
script is taking the vSAN WSDL files as inputs and will output the following
three dll files:

   * VsanhealthService.dll
   * VsanhealthService.XmlSerializers.dll

The generated DLL files can be directly consumed as the C# library for
accessing vSAN 6.6 APIs and all prior versions. For the detailed API usage,
please check the vSAN 6.6 API documentation.
'''

import os
import shutil
import re
import argparse

VSANHEALTH_STUB_FILE = "VsanhealthService.cs"
VSANHEALTH_VSANNS_STUB_FILE = "VsanhealthServiceVsan.cs"
VSANHEALTH_VSAN66NS_STUB_FILE = "VsanhealthServiceVsan66.cs"
VSANHEALTH_VIMNS_STUB_FILE = "VsanhealthServiceVim25.cs"
VSANHEALTH_DO_STUB_FILE = "VsanhealthServiceDO.cs"

INDENTATION  = ' ' * 4

VSANHEALTH_STUB_HEADER = '''
namespace VsanHealthApi {
    using System.Xml.Serialization;
    using System.Web.Services;
    using System.ComponentModel;
    using System.Web.Services.Protocols;
    using System.Net;
    using System;
    using System.Diagnostics;

    public partial class VsanhealthService
    {
        public const string VSAN_VMODL_NAMESPACE = "urn:vsan";
        public const string VSAN66_VMODL_NAMESPACE = "urn:vsan/6.6";
        public const string VIM25_VMODL_NAMESPACE = "urn:vim25";

        private VsanhealthServiceVsan vsanhealthVsanConn = null;
        private VsanhealthServiceVsan66 vsanhealthVsan66Conn = null;
        private VsanhealthServiceVim25 vsanhealthVimConn = null;
        public string ns = null;

        public VsanhealthService() { }
        public bool ConnectVsanService(string targetNs, string Url, CookieContainer cookie, int timeout)
        {
            if (targetNs == VSAN_VMODL_NAMESPACE)
            {
                vsanhealthVsanConn = new VsanhealthServiceVsan();
                vsanhealthVsanConn.Url = Url;
                vsanhealthVsanConn.CookieContainer = cookie;
                vsanhealthVsanConn.Timeout = timeout;
            }
            else if (targetNs == VSAN66_VMODL_NAMESPACE)
            {
                vsanhealthVsan66Conn = new VsanhealthServiceVsan66();
                vsanhealthVsan66Conn.Url = Url;
                vsanhealthVsan66Conn.CookieContainer = cookie;
                vsanhealthVsan66Conn.Timeout = timeout;
            }
            else if (targetNs == VIM25_VMODL_NAMESPACE)
            {
                vsanhealthVimConn = new VsanhealthServiceVim25();
                vsanhealthVimConn.Url = Url;
                vsanhealthVimConn.CookieContainer = cookie;
                vsanhealthVimConn.Timeout = timeout;
            }
            else
            {
                Console.WriteLine("Cannot recognize namespace '" + targetNs + "': supported namespaces are 'urn:vsan' and 'urn:vim25'");
                return false;
            }
            ns = targetNs;
            return true;
        }

        private bool Connected()
        {
            if ((ns == VSAN_VMODL_NAMESPACE && vsanhealthVsanConn != null) || (ns == VIM25_VMODL_NAMESPACE && vsanhealthVimConn != null) || (ns == VSAN66_VMODL_NAMESPACE && vsanhealthVsan66Conn != null))
            {
                return true;
            }
            else
            {
                Console.WriteLine("Did not connect to VsanHealth Service before!");
                return false;
            }
        }
'''

def parse_args():
   parser = argparse.ArgumentParser(description='vSAN C# library generator.')
   parser.add_argument('vsan_wsdl', type=str, help='Path to vsan.wsdl file.')
   parser.add_argument('vsanservice_wsdl', type=str, help='Path to vsanService.wsdl file.')
   parser.add_argument('-c', '--csc', type=str, default="csc.exe", help='Path to csc.exe executable.')
   parser.add_argument('-w', '--wsdl', type=str, default="wsdl.exe", help='Path to wsdl.exe executable.')
   parser.add_argument('-s', '--sgen', type=str, default="sgen.exe", help='Path to sgen.exe executable.')
   parser.add_argument('-v', '--svcutil', type=str, default="SvcUtil.exe", help='Path to SvcUtil.exe executable.')
   parser.add_argument('-m', '--msDll', type=str, default="Microsoft.Web.Services3.dll", help='Path to Microsoft.Web.Services3.dll library')
   args = parser.parse_args()
   return args

def run_cmd(cmd):
   import subprocess
   print('Running "' + ' '.join(cmd) + '"...\n')
   subprocess.check_output(cmd)

def generate_wsdl_cs_stub(wsdl_bin, vsan_wsdl, vsanservice_wsdl):
   print("Generating vSAN C# Stubs from WSDL file...")
   cmd = [wsdl_bin, '/n:VsanHealthApi', '/l:CS', vsan_wsdl, vsanservice_wsdl]
   run_cmd(cmd)

def generate_xmlserializer_dll(csc_bin, sgen_bin, msweb_dll):
   print("Generating vSAN C# SDK XML serializers...")
   cmd = [csc_bin, '/t:library', '/out:VsanhealthService.dll', '/r:' + msweb_dll,
          VSANHEALTH_STUB_FILE, VSANHEALTH_VSANNS_STUB_FILE,
          VSANHEALTH_VIMNS_STUB_FILE, VSANHEALTH_DO_STUB_FILE,
          VSANHEALTH_VSAN66NS_STUB_FILE]
   run_cmd(cmd)
   cmd = [sgen_bin, '/p', 'VsanhealthService.dll']
   run_cmd(cmd)

def generate_dll(csc_bin, msweb_dll):
   print("Generating vSAN C# SDK DLL...")
   cmd = [csc_bin, '/t:library', '/out:VsanhealthService.dll', '/r:' + msweb_dll,
          VSANHEALTH_STUB_FILE, VSANHEALTH_VSANNS_STUB_FILE,
          VSANHEALTH_VIMNS_STUB_FILE, VSANHEALTH_DO_STUB_FILE,
          VSANHEALTH_VSAN66NS_STUB_FILE]
   run_cmd(cmd)

def backup_stubs():
   if not os.path.isfile(VSANHEALTH_STUB_FILE):
      print("Cannot find the stub %s file in current directory" %
            VSANHEALTH_STUB_FILE)
      exit(1)

   # Backup the stub to a backup file for future restore;
   shutil.copyfile(VSANHEALTH_STUB_FILE, (VSANHEALTH_STUB_FILE + ".bak"))

def generate_methods_and_stubs():
   # Split the original stub file into the following three files:
   #
   # VSANHEALTH_VSANNS_STUB_FILE only contains all the methods with the
   #                             namespace under "urn:vsan".
   # VSANHEALTH_VIMNS_STUB_FILE  only contains all the methods with the
   #                             namespace under "urn:vim25".
   # VSANHEALTH_VSAN66NS_STUB_FILE  only contains all the methods with the
   #                                namespace under "urn:vsan/6.6".
   # VSANHEALTH_DO_STUB_FILE     only contains data objects without the
   #                             namespace annotations, i.e., ns neutral.
   #
   # Remove all the data objects from the VSANHEALTH_VSANNS_STUB_FILE
   # and the VSANHEALTH_VIMNS_STUB_FILE.
   for stub_file in [VSANHEALTH_VSANNS_STUB_FILE, VSANHEALTH_VIMNS_STUB_FILE,
           VSANHEALTH_VSAN66NS_STUB_FILE]:
      new_file = open(stub_file, "w", encoding='UTF-8')
      with open(VSANHEALTH_STUB_FILE, "r", encoding='UTF-8') as f:
         vsanservice_found = False
         for line in f:
            if not vsanservice_found:
               if "[System.CodeDom.Compiler" in line:
                  vsanservice_found = True
            else:
               # Here we hit some other data objects so stop copying these
               # stubs to the stub file only contains the methods.
               if "[System.CodeDom.Compiler" in line:
                  break
            if stub_file == VSANHEALTH_VIMNS_STUB_FILE:
               # Use only vim25 namespace in the VIM stub file.
               line = re.sub(r'urn:vsan/.*"', 'urn:vim25/6.5"', line)
               line = re.sub(r'urn:vsan', 'urn:vim25', line)
               line = re.sub(r'VsanhealthService', 'VsanhealthServiceVim25', line)
            elif stub_file == VSANHEALTH_VSAN66NS_STUB_FILE:
               line = re.sub(r'urn:vsan/.*"', 'urn:vsan/6.6"', line)
               line = re.sub(r'VsanhealthService', 'VsanhealthServiceVsan66', line)
            else:
               line = re.sub(r'VsanhealthService', 'VsanhealthServiceVsan', line)
            new_file.write(line)
      new_file.write("}")
      new_file.close()

   new_file = open(VSANHEALTH_DO_STUB_FILE, "w", encoding='UTF-8')
   with open(VSANHEALTH_STUB_FILE, "r", encoding='UTF-8') as f:
      vsanservice_found = False
      do_found = False
      for line in f:
         if not do_found and not vsanservice_found:
            if "[System.CodeDom.Compiler" in line:
               vsanservice_found = True
               continue
         else:
            # Here we hit some other data objects so stop copying these
            # stubs to the stub file only contains the methods.
            if not do_found:
               if "[System.CodeDom.Compiler" in line:
                  do_found = True
               else:
                  continue
         new_file.write(line)
   new_file.close()

def generate_stub_entry():
   stub_file = open(VSANHEALTH_STUB_FILE, "w", encoding='UTF-8')
   stub_file.write(VSANHEALTH_STUB_HEADER)
   with open(VSANHEALTH_VSANNS_STUB_FILE, "r", encoding='UTF-8') as f:
      for line in f:
         if (" public " in line and
               "partial class" not in line and
               "public event" not in line and
               "VsanhealthService" not in line):
            func_info = line.split('(', 1)
            identifiers = re.sub(r' new', '', func_info[0])
            stub_file.write(identifiers)
            func_name = identifiers.strip().split(' ')[-1]
            return_type = identifiers.strip().split(' ')[-2]
            stub_file.write('(')
            param_type_list = []
            param_name_list = []
            # Value in fun_info[1] has two styles.
            # 1 funName(
            #           type param1,
            #           type param2,){....}
            # 2 funName(type param1, type param2){...}
            # If fun_info[1] is the first style
            # we need to convert it to the second style.
            if '{' not in func_info[1]:
               param_line = next(f)
               param_info = param_line.strip()
               while '{' not in param_line:
                  param_line = next(f)
                  param_info = param_info + (param_line.strip())
               param_info = param_info.split('{')[0].strip()
            else:
               param_info = func_info[1].split('{')[0].strip()

            for params in param_info.split(','):
               param_list = params.split(' ')
               param_type_list.append(param_list[-2])
               param_name_list.append(param_list[-1].split(')')[0])

            for i in range(0, len(param_type_list)-1):
               stub_file.write(param_type_list[i] + ' ')
               stub_file.write(param_name_list[i] + ', ')
            stub_file.write(param_type_list[-1] + ' ')
            stub_file.write(param_name_list[-1] + ') {\n')

            return_mark = 'return '
            if return_type == "void":
               return_mark = ''

            stub_file.write(INDENTATION * 3 + 'if (!Connected()) { throw new InvalidOperationException("Not Connected!"); }\n')
            stub_file.write(INDENTATION * 3 + 'if (ns == VSAN_VMODL_NAMESPACE) {\n')
            stub_file.write(INDENTATION * 4 + return_mark + 'vsanhealthVsanConn.')
            stub_file.write(func_name + '(')
            for param_name in param_name_list[:-1]:
               stub_file.write(param_name + ', ')
            stub_file.write(param_name_list[-1] + ');\n')
            stub_file.write(INDENTATION * 3 + '} else if (ns == VSAN66_VMODL_NAMESPACE) {\n')
            stub_file.write(INDENTATION * 4 + return_mark + 'vsanhealthVsan66Conn.')
            stub_file.write(func_name + '(')
            for param_name in param_name_list[:-1]:
               stub_file.write(param_name + ', ')
            stub_file.write(param_name_list[-1] + ');\n')
            stub_file.write(INDENTATION * 3 + '} else {\n')
            stub_file.write(INDENTATION * 4 + return_mark + 'vsanhealthVimConn.')
            stub_file.write(func_name + '(')
            for param_name in param_name_list[:-1]:
               stub_file.write(param_name + ', ')
            stub_file.write(param_name_list[-1] + ');\n')
            stub_file.write(INDENTATION * 3 + '}\n')
            stub_file.write(INDENTATION * 2 + '}\n')
            stub_file.write('\n')

   stub_file.write(INDENTATION + "}\n")
   stub_file.write("}")
   stub_file.close

def generate_stub_with_ns():
   print("Generating new stubs with vim25 and vsan namespaces...")
   backup_stubs()
   generate_methods_and_stubs()
   generate_stub_entry()

def update_stub_dll_pointer():
   print("Updating the vSAN C# stubs to use the XML serializer...")
   for method_file in [VSANHEALTH_VSANNS_STUB_FILE, VSANHEALTH_VIMNS_STUB_FILE, VSANHEALTH_VSAN66NS_STUB_FILE]:
      backup_file = method_file + ".bak"
      shutil.move(method_file, backup_file)
      stub_file = open(method_file, 'w', encoding='UTF-8')
      with open(backup_file, "r", encoding='UTF-8') as f:
         for line in f:
            line = re.sub(r'\[System.Xml.Serialization.XmlIncludeAttribute', '// [System.Xml.Serialization.XmlIncludeAttribute', line)
            if "Microsoft.Web.Services3.WebServicesClientProtocol" in line:
               stub_file.write(INDENTATION + '[System.Xml.Serialization.XmlSerializerAssemblyAttribute(AssemblyName = "VsanhealthService.XmlSerializers")]\n')
            stub_file.write(line)
      stub_file.close()

def cleanup():
   print("Cleaning up...")
   files = os.listdir(os.curdir)
   for f in files:
      if (os.path.isfile(f) and (f.endswith('.cs') or f.endswith('.cs.bak'))):
         os.remove(f)

def main():
   args = parse_args()

   generate_wsdl_cs_stub(args.wsdl, args.vsan_wsdl, args.vsanservice_wsdl)
   generate_stub_with_ns()
   generate_xmlserializer_dll(args.csc, args.sgen, args.msDll)
   update_stub_dll_pointer()
   generate_dll(args.csc, args.msDll)

   cleanup()

if __name__ == "__main__":
   main()
