/*
 * VocaloSysUtil.s
 * Copyright (c) 2009 kbinani
 *
 * This file is part of Boare.Lib.Vsq.
 *
 * Boare.Lib.Vsq is free software; you can redistribute it and/or
 * modify it under the terms of the BSD License.
 *
 * Boare.Lib.Vsq 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.
 */
using System;
using System.IO;
using System.Collections.Generic;
using Microsoft.Win32;

namespace Boare.Lib.Vsq {

    using boolean = Boolean;

    public static class VocaloSysUtil{
        private static SingerConfigSys s_singer_config_sys1;
        private static SingerConfigSys s_singer_config_sys2;
        private static ExpressionConfigSys s_exp_config_sys1;
        private static ExpressionConfigSys s_exp_config_sys2;
        private static String s_path_vsti1;
        private static String s_path_vsti2;
        private static String s_path_editor1;
        private static String s_path_editor2;

        static VocaloSysUtil() {
            Vector<String> dir1 = new Vector<String>();
            RegistryKey key1 = Registry.LocalMachine.OpenSubKey( "SOFTWARE\\VOCALOID", false );
            String header1 = "HKLM\\SOFTWARE\\VOCALOID";
            print( key1, header1, dir1 );
            key1.Close();
            String path_voicedb1;
            String path_expdb1;
            Vector<String> installed_singers1 = new Vector<String>();
            extract( dir1,
                     header1, 
                     out s_path_vsti1, 
                     out path_voicedb1, 
                     out path_expdb1, 
                     out s_path_editor1,
                     installed_singers1 );
            s_singer_config_sys1 = new SingerConfigSys( path_voicedb1, installed_singers1.ToArray() );
            s_exp_config_sys1 = new ExpressionConfigSys( path_expdb1 );

            Vector<String> dir2 = new Vector<String>();
            RegistryKey key2 = Registry.LocalMachine.OpenSubKey( "SOFTWARE\\VOCALOID2", false );
            String header2 = "HKLM\\SOFTWARE\\VOCALOID2";
            print( key2, header2 , dir2 );
            key2.Close();
            String path_voicedb2;
            String path_expdb2;
            Vector<String> installed_singers2 = new Vector<String>();
            extract( dir2, 
                     header2, 
                     out s_path_vsti2, 
                     out path_voicedb2, 
                     out path_expdb2, 
                     out s_path_editor2,
                     installed_singers2 );
            s_singer_config_sys2 = new SingerConfigSys( path_voicedb2, installed_singers2.ToArray() );
            s_exp_config_sys2 = new ExpressionConfigSys( path_expdb2 );
        }

        private static void extract( Vector<String> dir,
                                     String header,
                                     out String path_vsti, 
                                     out String path_voicedb, 
                                     out String path_expdb, 
                                     out String path_editor,
                                     Vector<String> installed_singers ) {
            Vector<String> application = new Vector<String>();
            Vector<String> expression = new Vector<String>();
            Vector<String> voice = new Vector<String>();
            path_vsti = "";
            path_expdb = "";
            path_voicedb = "";
            path_editor = "";
            foreach ( String s in dir ) {
                if ( s.StartsWith( header + "\\APPLICATION" ) ) {
                    application.Add( s.Substring( (header + "\\APPLICATION").Length ) );
                } else if ( s.StartsWith( header + "\\DATABASE\\EXPRESSION" ) ) {
                    expression.Add( s.Substring( (header + "\\DATABASE\\EXPRESSION").Length ) );
                } else if ( s.StartsWith( header + "\\DATABASE\\VOICE" ) ) {
                    voice.Add( s.Substring( (header + "\\DATABASE\\VOICE\\").Length ) );
                }
            }

            // path_vstiを取得
            foreach ( String s in application ) {
                String[] spl = s.Split( '\t' );
                if ( spl.Length >= 3 && spl[1].Equals( "PATH" ) ){
                    if ( spl[2].ToLower().EndsWith( ".dll" ) ) {
                        path_vsti = spl[2];
                    } else if ( spl[2].ToLower().EndsWith( ".exe" ) ) {
                        path_editor = spl[2];
                    }
                }
            }

            // path_vicedbを取得
            Vector<String> voice_ids = new Vector<String>();
            // 最初はpath_voicedbの取得と、id（BHXXXXXXXXXXXXXXXX）のようなシリアルを取得
            foreach ( String s in voice ) {
                String[] spl = s.Split( '\t' );
                if ( spl.Length >= 2 ) {
                    if ( spl[0].Equals( "VOICEDIR" ) ) {
                        path_voicedb = spl[1];
                    } else if ( spl.Length >= 3 ) {
                        String[] spl2 = spl[0].Split( '\\' );
                        if ( spl2.Length == 1 ) {
                            if ( !voice_ids.Contains( spl2[0] ) ) {
                                voice_ids.Add( spl2[0] );
                            }
                        }
                    }
                }
            }
            // 取得したシリアルを元に、installed_singersを取得
            foreach ( String s in voice_ids ) {
                String install_dir = "";
                foreach ( String s2 in voice ) {
                    if ( s2.StartsWith( header + "\\" + s + "\t" ) ) {
                        String[] spl = s2.Split( '\t' );
                        if ( spl.Length >= 3 && spl[1].Equals( "INSTALLDIR" ) ) {
                            install_dir = Path.Combine( spl[2], s );
                            break;
                        }
                    }
                }
                if ( install_dir.Equals( "" ) ) {
                    install_dir = Path.Combine( path_voicedb, s );
                }
                installed_singers.Add( install_dir );
            }

            // path_expdbを取得
            Vector<String> exp_ids = new Vector<String>();
            // 最初はpath_expdbの取得と、id（BHXXXXXXXXXXXXXXXX）のようなシリアルを取得
            foreach ( String s in expression ) {
                String[] spl = s.Split( '\t' );
                if ( spl.Length >= 2 ) {
                    if ( spl[0].Equals( "EXPRESSIONDIR" ) ) {
                        path_expdb = spl[1];
                    } else if ( spl.Length >= 3 ) {
                        String[] spl2 = spl[0].Split( '\\' );
                        if ( spl2.Length == 1 ) {
                            if ( !exp_ids.Contains( spl2[0] ) ) {
                                exp_ids.Add( spl2[0] );
                            }
                        }
                    }
                }
            }
            // 取得したシリアルを元に、installed_singersを取得
            /*foreach ( String s in exp_ids ) {
                String install_dir = "";
                foreach ( String s2 in expression ) {
                    if ( s2.StartsWith( header + "\\" + s + "\t" ) ) {
                        String[] spl = s2.Split( '\t' );
                        if ( spl.Length >= 3 && spl[1].Equals( "INSTALLDIR" ) ) {
                            install_dir = Path.Combine( spl[2], s );
                            break;
                        }
                    }
                }
                if ( install_dir.Equals( "" ) ) {
                    install_dir = Path.Combine( path_expdb, s );
                }
                installed_singers.Add( install_dir );
            }*/

#if DEBUG
            Console.WriteLine( "path_vsti=" + path_vsti );
            Console.WriteLine( "path_voicedb=" + path_voicedb );
            Console.WriteLine( "path_expdb=" + path_expdb );
            Console.WriteLine( "installed_singers=" );
            foreach ( String s in installed_singers ) {
                Console.WriteLine( "    " + s );
            }
#endif
        }

        // レジストリkey内の値を再帰的に検索し、ファイルfpに順次出力する
        private static void print( RegistryKey key, String parent_name, Vector<String> list ){
            // 直下のキー内を再帰的にリストアップ
            String[] subkeys = key.GetSubKeyNames();
            foreach( String s in subkeys ){
                RegistryKey subkey = key.OpenSubKey( s, false );
                print( subkey, parent_name + "\\" + s, list );
                subkey.Close();
            }

            // 直下の値を出力
            String[] valuenames = key.GetValueNames();
            foreach( String s in valuenames ){
                RegistryValueKind kind = key.GetValueKind( s );
                if ( kind == RegistryValueKind.String ){
                    String str = parent_name + "\t" + s + "\t" + (String)key.GetValue( s );
                    list.Add( str );
                }
            }
        }

        /// <summary>
        /// Gets the name of original singer of specified program change.
        /// </summary>
        /// <param name="singer"></param>
        /// <returns></returns>
        public static String getOriginalSinger1( String singer ) {
            string voiceidstr = "";
            SingerConfig[] singer_configs = s_singer_config_sys1.getSingerConfigs();
            for ( int i = 0; i < singer_configs.Length; i++ ) {
                if ( singer.Equals( singer_configs[i].VOICENAME ) ) {
                    voiceidstr = singer_configs[i].VOICEIDSTR;
                    break;
                }
            }
            if ( voiceidstr.Equals( "" ) ) {
                return "";
            }
            SingerConfig[] installed_singers = s_singer_config_sys1.getInstalledSingers();
            for ( int i = 0; i < installed_singers.Length; i++ ) {
                if ( voiceidstr.Equals( installed_singers[i].VOICEIDSTR ) ) {
                    return installed_singers[i].VOICENAME;
                }
            }
            return "";
        }

        /// <summary>
        /// Gets the name of original singer of specified program change.
        /// </summary>
        /// <param name="singer"></param>
        /// <returns></returns>
        public static String getOriginalSinger2( String singer ) {
            string voiceidstr = "";
            SingerConfig[] singer_configs = s_singer_config_sys2.getSingerConfigs();
            for ( int i = 0; i < singer_configs.Length; i++ ) {
                if ( singer.Equals( singer_configs[i].VOICENAME ) ) {
                    voiceidstr = singer_configs[i].VOICEIDSTR;
                    break;
                }
            }
            if ( voiceidstr.Equals( "" ) ) {
                return "";
            }
            SingerConfig[] installed_singers = s_singer_config_sys2.getInstalledSingers();
            for ( int i = 0; i < installed_singers.Length; i++ ) {
                if ( voiceidstr.Equals( installed_singers[i].VOICEIDSTR ) ) {
                    return installed_singers[i].VOICENAME;
                }
            }
            return "";
        }

        public static VsqID getSingerID1( String singer ) {
            return s_singer_config_sys1.getSingerID( singer );
        }

        public static VsqID getSingerID2( String singer ) {
            return s_singer_config_sys2.getSingerID( singer );
        }

        public static String getEditorPath1() {
            return s_path_editor1;
        }

        public static String getEditorPath2() {
            return s_path_editor2;
        }

        public static String getDllPathVsti1() {
            return s_path_vsti1;
        }

        public static String getDllPathVsti2() {
            return s_path_vsti2;
        }

        public static SingerConfig[] getSingerConfigs1() {
            return s_singer_config_sys1.getSingerConfigs();
        }

        public static SingerConfig[] getSingerConfigs2() {
            return s_singer_config_sys2.getSingerConfigs();
        }

        /// <summary>
        /// Gets the voice language of specified program change
        /// </summary>
        /// <param name="name">name of singer</param>
        /// <returns></returns>
        public static VsqVoiceLanguage getLanguageFromName( string name ) {
            switch ( name.ToLower() ) {
                case "meiko":
                case "kaito":
                case "miku":
                case "rin":
                case "len":
                case "rin_act2":
                case "len_act2":
                case "gackpoid":
                case "luka_jpn":
                case "megpoid":
                    return VsqVoiceLanguage.Japanese;
                case "sweet_ann":
                case "prima":
                case "luka_eng":
                case "sonika":
                    return VsqVoiceLanguage.English;
            }
            return VsqVoiceLanguage.Default;
        }

        public static double getAmplifyCoeffFromPanLeft( int pan ) {
            return pan / -64.0 + 1.0;
        }

        public static double getAmplifyCoeffFromPanRight( int pan ) {
            return pan / 64.0 + 1.0;
        }

        public static double getAmplifyCoeffFromFeder( int feder ) {
            return Math.Exp( -1.26697245e-02 + 1.18448420e-01 * feder / 10.0 );
        }

        /// <summary>
        /// Transform the byte array(length=8) to unsigned long, assuming that the byte array is little endian.
        /// </summary>
        /// <param name="oct"></param>
        /// <returns></returns>
        public static ulong makelong_le( byte[] oct ) {
            return (ulong)oct[7] << 56 | (ulong)oct[6] << 48 | (ulong)oct[5] << 40 | (ulong)oct[4] << 32 | (ulong)oct[3] << 24 | (ulong)oct[2] << 16 | (ulong)oct[1] << 8 | (ulong)oct[0];
        }
    }

    public static class VocaloSysUtil_ {
        private static bool s_initialized = false;

        private static string s_dll_path2 = "";
        private static string s_editor_path2 = "";
        private static string s_voicedbdir2 = "";
        private static List<SingerConfig> s_installed_singers2 = new List<SingerConfig>();
        private static List<SingerConfig> s_singer_configs2 = new List<SingerConfig>();

        private static string s_dll_path1 = "";
        private static string s_editor_path1 = "";
        private static string s_voicedbdir1 = "";
        private static List<SingerConfig> s_installed_singers1 = new List<SingerConfig>();
        private static List<SingerConfig> s_singer_configs1 = new List<SingerConfig>();

        private const int MAX_SINGERS = 0x4000;

        static VocaloSysUtil_() {
            init_vocalo2();
            init_vocalo1();
        }

        /// <summary>
        /// Gets the name of original singer of specified program change.
        /// </summary>
        /// <param name="singer"></param>
        /// <returns></returns>
        public static string getOriginalSinger1( string singer ) {
            string voiceidstr = "";
            for ( int i = 0; i < s_singer_configs1.Count; i++ ) {
                if ( singer == s_singer_configs1[i].VOICENAME ) {
                    voiceidstr = s_singer_configs1[i].VOICEIDSTR;
                }
            }
            if ( voiceidstr == "" ) {
                return "";
            }
            for ( int i = 0; i < s_installed_singers1.Count; i++ ) {
                if ( voiceidstr == s_installed_singers1[i].VOICEIDSTR ) {
                    return s_installed_singers1[i].VOICENAME;
                }
            }
            return "";
        }

        /// <summary>
        /// Gets the name of original singer of specified program change.
        /// </summary>
        /// <param name="singer"></param>
        /// <returns></returns>
        public static string getOriginalSinger2( string singer ) {
            string voiceidstr = "";
            for ( int i = 0; i < s_singer_configs2.Count; i++ ) {
                if ( singer == s_singer_configs2[i].VOICENAME ) {
                    voiceidstr = s_singer_configs2[i].VOICEIDSTR;
                }
            }
            if ( voiceidstr == "" ) {
                return "";
            }
            for ( int i = 0; i < s_installed_singers2.Count; i++ ) {
                if ( voiceidstr == s_installed_singers2[i].VOICEIDSTR ) {
                    return s_installed_singers2[i].VOICENAME;
                }
            }
            return "";
        }

        /// <summary>
        /// Gets the voice language of specified program change
        /// </summary>
        /// <param name="name">name of singer</param>
        /// <returns></returns>
        public static VsqVoiceLanguage getLanguageFromName( string name ) {
            switch ( name ) {
                case "MEIKO":
                case "KAITO":
                case "Miku":
                case "Rin":
                case "Len":
                case "Rin_ACT2":
                case "Len_ACT2":
                case "Gackpoid":
                case "Luka_JPN":
                case "Megpoid":
                    return VsqVoiceLanguage.Japanese;
                case "Sweet_Ann":
                case "Prima":
                case "Luka_ENG":
                    return VsqVoiceLanguage.English;
            }
            return VsqVoiceLanguage.Default;
        }

        public static VsqID getSingerID1( string singer_name ) {
            VsqID ret = new VsqID( 0 );
            ret.type = VsqIDType.Singer;
            SingerConfig sc = null;
            for ( int i = 0; i < s_singer_configs1.Count; i++ ) {
                if ( s_singer_configs1[i].VOICENAME == singer_name ) {
                    sc = s_singer_configs1[i];
                    break;
                }
            }
            if ( sc == null ) {
                sc = new SingerConfig();
            }
            int lang = 0;
            foreach ( SingerConfig sc2 in s_installed_singers1 ) {
                if ( sc.VOICEIDSTR == sc2.VOICEIDSTR ) {
                    lang = (int)getLanguageFromName( sc.VOICENAME );
                    break;
                }
            }
            ret.IconHandle = new IconHandle();
            ret.IconHandle.IconID = "$0701" + sc.Program.ToString( "0000" );
            ret.IconHandle.IDS = sc.VOICENAME;
            ret.IconHandle.Index = 0;
            ret.IconHandle.Language = lang;
            ret.IconHandle.Length = 1;
            ret.IconHandle.Original = sc.Original;
            ret.IconHandle.Program = sc.Program;
            ret.IconHandle.Caption = "";
            return ret;
        }

        public static VsqID getSingerID2( string singer_name ) {
            VsqID ret = new VsqID( 0 );
            ret.type = VsqIDType.Singer;
            SingerConfig sc = null;
            for ( int i = 0; i < s_singer_configs2.Count; i++ ) {
                if ( s_singer_configs2[i].VOICENAME == singer_name ) {
                    sc = s_singer_configs2[i];
                    break;
                }
            }
            if ( sc == null ) {
                sc = new SingerConfig();
            }
            int lang = 0;
            foreach ( SingerConfig sc2 in s_installed_singers2 ) {
                if ( sc.VOICEIDSTR == sc2.VOICEIDSTR ) {
                    lang = (int)getLanguageFromName( sc.VOICENAME );
                    break;
                }
            }
            ret.IconHandle = new IconHandle();
            ret.IconHandle.IconID = "$0701" + sc.Program.ToString( "0000" );
            ret.IconHandle.IDS = sc.VOICENAME;
            ret.IconHandle.Index = 0;
            ret.IconHandle.Language = lang;
            ret.IconHandle.Length = 1;
            ret.IconHandle.Original = sc.Original;
            ret.IconHandle.Program = sc.Program;
            ret.IconHandle.Caption = "";
            return ret;
        }

        public static SingerConfig[] getSingerConfigs1() {
            return s_singer_configs1.ToArray();
        }

        public static SingerConfig[] getSingerConfigs2() {
            return s_singer_configs2.ToArray();
        }

        public static double getAmplifyCoeffFromPanLeft( int pan ) {
            return pan / -64.0 + 1.0;
        }

        public static double getAmplifyCoeffFromPanRight( int pan ) {
            return pan / 64.0 + 1.0;
        }

        public static double getAmplifyCoeffFromFeder( int feder ) {
            return Math.Exp( -1.26697245e-02 + 1.18448420e-01 * feder / 10.0 );
        }

        public static string getEditorPath2() {
            return s_editor_path2;
        }

        public static string getEditorPath1() {
            return s_editor_path1;
        }

        public static string getDllPathVsti2() {
            return s_dll_path2;
        }

        public static string getDllPathVsti1() {
            return s_dll_path1;
        }

        /// <summary>
        /// VOCALOID1システムのプロパティを取得
        /// </summary>
        private static void init_vocalo1() {
            // vocaloid1 dll path
            RegistryKey v1application = null;
            v1application = Registry.LocalMachine.OpenSubKey( "SOFTWARE\\VOCALOID\\APPLICATION", false );
            if ( v1application != null ) {
                string[] keys = v1application.GetSubKeyNames();
                for ( int i = 0; i < keys.Length; i++ ) {
                    RegistryKey key = v1application.OpenSubKey( keys[i], false );
                    if ( key != null ) {
                        string name = (string)key.GetValue( "PATH" );
                        if ( name.ToLower().EndsWith( "\\vocaloid.dll" ) ) {
                            s_dll_path1 = name;
                        } else if ( name.ToLower().EndsWith( "\\vocaloid.exe" ) ) {
                            s_editor_path1 = name;
                        }
                        key.Close();
                    }
                }
                v1application.Close();
            }

            // voicedbdir for vocaloid1
            RegistryKey v1database = Registry.LocalMachine.OpenSubKey( "SOFTWARE\\VOCALOID\\DATABASE\\VOICE", false );
            if ( v1database != null ) {
                s_voicedbdir1 = (string)v1database.GetValue( "VOICEDIR", "" );
#if DEBUG
                Console.WriteLine( "s_voicedbdir1=" + s_voicedbdir1 );
#endif
                // インストールされている歌手のVOICEIDSTRを列挙
                string[] singer_voiceidstrs = v1database.GetSubKeyNames();
                List<string> vvoice_keys = new List<string>();
                List<SingerConfig> vvoice_values = new List<SingerConfig>();
                foreach ( string voiceidstr in singer_voiceidstrs ) {
                    RegistryKey singer = v1database.OpenSubKey( voiceidstr );
                    if ( singer == null ) {
                        continue;
                    }
                    RegistryKey vvoice = singer.OpenSubKey( "vvoice" );
                    if ( vvoice != null ) {
                        string[] vvoices = vvoice.GetValueNames();

                        // インストールされた歌手の.vvdを読みにいく
                        // installdir以下の、拡張子.vvdのファイルを探す
                        foreach ( string file in Directory.GetFiles( Path.Combine( s_voicedbdir1, voiceidstr ), "*.vvd" ) ) {
                            SingerConfig config = SingerConfig.fromVvd( file, 0 ); //とりあえずプログラムチェンジは0
                            s_installed_singers1.Add( config );
                        }

                        // vvoice*.vvdを読みにいく。
                        foreach ( string s in vvoices ) {
#if DEBUG
                            Console.WriteLine( "s=" + s );
#endif
                            string file = Path.Combine( s_voicedbdir1, s + ".vvd" );
                            if ( File.Exists( file ) ) {
                                SingerConfig config = SingerConfig.fromVvd( file, 0 );
                                vvoice_keys.Add( s );
                                vvoice_values.Add( config );
                            }
                        }
                    }
                    singer.Close();
                }

                // voice.mapを読み込んで、s_singer_configs1のプログラムチェンジを更新する
                string map = Path.Combine( s_voicedbdir1, "voice.map" );
                if ( File.Exists( map ) ) {
                    using ( FileStream fs = new FileStream( map, FileMode.Open, FileAccess.Read ) ) {
                        byte[] dat = new byte[8];
                        fs.Seek( 0x20, SeekOrigin.Begin );
                        for ( int i = 0; i < MAX_SINGERS; i++ ) {
                            fs.Read( dat, 0, 8 );
                            ulong value = makelong_le( dat );
                            if ( value >= 1 ) {
#if DEBUG
                                Console.WriteLine( "value=" + value );
#endif
                                for ( int j = 0; j < vvoice_keys.Count; j++ ) {
                                    if ( vvoice_keys[j] == "vvoice" + value ) {
                                        vvoice_values[j].Program = i;
                                    }
                                }
                            }
                        }
                    }
                }

                // s_installed_singers1のSingerConfigのProgramとOriginalを適当に頒番する
                for ( int i = 0; i < s_installed_singers1.Count; i++ ) {
                    s_installed_singers1[i].Program = i;
                    s_installed_singers1[i].Original = i;
                }

                // s_singer_configs1を更新
                for ( int i = 0; i < vvoice_values.Count; i++ ) {
                    for ( int j = 0; j < s_installed_singers1.Count; j++ ) {
                        if ( vvoice_values[i].VOICEIDSTR == s_installed_singers1[j].VOICEIDSTR ) {
                            vvoice_values[i].Original = s_installed_singers1[j].Program;
                            break;
                        }
                    }
                    s_singer_configs1.Add( vvoice_values[i] );
                }
                v1database.Close();
            }
#if DEBUG
            Console.WriteLine( "installed" );
            foreach ( SingerConfig sc in s_installed_singers1 ) {
                Console.WriteLine( "VOICENAME=" + sc.VOICENAME + "; VOICEIDSTR=" + sc.VOICEIDSTR + "; Program=" + sc.Program + "; Original=" + sc.Original );
            }
            Console.WriteLine( "singer configs" );
            foreach ( SingerConfig sc in s_singer_configs1 ) {
                Console.WriteLine( "VOICENAME=" + sc.VOICENAME + "; VOICEIDSTR=" + sc.VOICEIDSTR + "; Program=" + sc.Program + "; Original=" + sc.Original );
            }
#endif
        }

        /// <summary>
        /// VOCALOID2システムのプロパティを取得
        /// </summary>
        private static void init_vocalo2() {
            // 最初はvstiとeditorのパスを取得
            RegistryKey v2application = Registry.LocalMachine.OpenSubKey( "SOFTWARE\\VOCALOID2\\APPLICATION", false );
            if ( v2application == null ) {
                v2application = Registry.LocalMachine.OpenSubKey( "SOFTWARE\\VOCALOID2_DEMO\\APPLICATION", false );
            }
            if ( v2application != null ) {
                string[] keys = v2application.GetSubKeyNames();
                for ( int i = 0; i < keys.Length; i++ ) {
                    RegistryKey key = v2application.OpenSubKey( keys[i], false );
                    if ( key != null ) {
                        string name = (string)key.GetValue( "PATH" );
                        if ( name.ToLower().EndsWith( "\\vocaloid2.dll" ) ) {
                            s_dll_path2 = name;
                        } else if ( name.ToLower().EndsWith( "\\vocaloid2_demo.dll" ) ) {
                            s_dll_path2 = name;
                        } else if ( name.ToLower().EndsWith( "\\vocaloid2.exe" ) ) {
                            s_editor_path2 = name;
                        }
                        key.Close();
                    }
                }
                v2application.Close();
            }

            // 歌声データベースを取得
            RegistryKey v2database = Registry.LocalMachine.OpenSubKey( "SOFTWARE\\VOCALOID2\\DATABASE\\VOICE", false );
            if ( v2database != null ) {
                // データベース（というよりもvoice.map）が保存されているパスを取得
                s_voicedbdir2 = (string)v2database.GetValue( "VOICEDIR", "" );
                // インストールされている歌手のVOICEIDSTRを列挙
                string[] singer_voiceidstrs = v2database.GetSubKeyNames();
                List<string> vvoice_keys = new List<string>();
                List<SingerConfig> vvoice_values = new List<SingerConfig>();
                foreach ( string voiceidstr in singer_voiceidstrs ) {
                    RegistryKey singer = v2database.OpenSubKey( voiceidstr );
                    if ( singer == null ) {
                        continue;
                    }
                    string installdir = (string)singer.GetValue( "INSTALLDIR", "" );
#if DEBUG
                    Console.WriteLine( "installdir=" + installdir );
#endif
                    RegistryKey vvoice = singer.OpenSubKey( "vvoice" );
                    if ( vvoice != null ) {
                        string[] vvoices = vvoice.GetValueNames();

                        // インストールされた歌手の.vvdを読みにいく
                        // installdir以下の、拡張子.vvdのファイルを探す
                        foreach ( string file in Directory.GetFiles( Path.Combine( installdir, voiceidstr ), "*.vvd" ) ) {
                            SingerConfig config = SingerConfig.fromVvd( file, 0 ); //とりあえずプログラムチェンジは0
                            s_installed_singers2.Add( config );
                        }

                        // vvoice*.vvdを読みにいく。場所は、installdirではなく、s_voicedbdir2
                        foreach ( string s in vvoices ) {
                            string file = Path.Combine( s_voicedbdir2, s + ".vvd" );
                            if ( File.Exists( file ) ) {
                                SingerConfig config = SingerConfig.fromVvd( file, 0 );
                                vvoice_keys.Add( s );
                                vvoice_values.Add( config );
                            }
                        }
                    }
                    singer.Close();
                }

                // voice.mapを読み込んで、s_singer_configs2のプログラムチェンジを更新する
                string map = Path.Combine( s_voicedbdir2, "voice.map" );
                if ( File.Exists( map ) ) {
                    using ( FileStream fs = new FileStream( map, FileMode.Open, FileAccess.Read ) ) {
                        byte[] dat = new byte[8];
                        fs.Seek( 0x20, SeekOrigin.Begin );
                        for ( int i = 0; i < MAX_SINGERS; i++ ) {
                            fs.Read( dat, 0, 8 );
                            ulong value = makelong_le( dat );
                            if ( value >= 1 ) {
#if DEBUG
                                Console.WriteLine( "value=" + value );
#endif
                                for ( int j = 0; j < vvoice_keys.Count; j++ ) {
                                    if ( vvoice_keys[j] == "vvoice" + value ) {
                                        vvoice_values[j].Program = i;
                                    }
                                }
                            }
                        }
                    }
                }

                // s_installed_singers2のSingerConfigのProgramとOriginalを適当に頒番する
                for ( int i = 0; i < s_installed_singers2.Count; i++ ) {
                    s_installed_singers2[i].Program = i;
                    s_installed_singers2[i].Original = i;
                }

                // s_singer_configs2を更新
                for ( int i = 0; i < vvoice_values.Count; i++ ) {
                    for ( int j = 0; j < s_installed_singers2.Count; j++ ) {
                        if ( vvoice_values[i].VOICEIDSTR == s_installed_singers2[j].VOICEIDSTR ) {
                            vvoice_values[i].Original = s_installed_singers2[j].Program;
                            break;
                        }
                    }
                    s_singer_configs2.Add( vvoice_values[i] );
                }
                v2database.Close();
            }
#if DEBUG
            Console.WriteLine( "installed" );
            foreach ( SingerConfig sc in s_installed_singers2 ) {
                Console.WriteLine( "VOICENAME=" + sc.VOICENAME + "; VOICEIDSTR=" + sc.VOICEIDSTR + "; Program=" + sc.Program + "; Original=" + sc.Original );
            }
            Console.WriteLine( "singer configs" );
            foreach ( SingerConfig sc in s_singer_configs2 ) {
                Console.WriteLine( "VOICENAME=" + sc.VOICENAME + "; VOICEIDSTR=" + sc.VOICEIDSTR + "; Program=" + sc.Program + "; Original=" + sc.Original );
            }
#endif
        }

        /// <summary>
        /// Transform the byte array(length=8) to unsigned long, assuming that the byte array is little endian.
        /// </summary>
        /// <param name="oct"></param>
        /// <returns></returns>
        public static ulong makelong_le( byte[] oct ) {
            return (ulong)oct[7] << 56 | (ulong)oct[6] << 48 | (ulong)oct[5] << 40 | (ulong)oct[4] << 32 | (ulong)oct[3] << 24 | (ulong)oct[2] << 16 | (ulong)oct[1] << 8 | (ulong)oct[0];
        }
    }

}
