/*
 * VsqUtil.java
 * Copyright (c) 2008 kbinani
 *
 * This file is part of jp.sourceforge.lipsync.vsq.
 *
 * jp.sourceforge.lipsync.vsq is free software; you can redistribute it and/or
 * modify it under the terms of the BSD License.
 *
 * jp.sourceforge.lipsync.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.
 */
package jp.sourceforge.lipsync.vsq;

import java.util.*;
    /// <summary>
    /// コンピュータにインストールされたVOCALOID2システムについての情報を取得するためのスタティック・ライブラリ。
    /// </summary>
    public class VsqUtil {
        /// <summary>
        /// VOCALOIDシステムの仕様上設定可能な歌手の最大数
        /// </summary>
        public final int MAX_SINGERS = 0x4000;

        private static String s_dll_path = "";
        private static boolean s_dll_path_done = false;
        private static String s_exp_db_dir = "";
        private static boolean s_exp_db_dir_done = false;
        private static Dictionary<int, SingerConfig> s_singer_configs = null;
        private static Vector<SingerConfig> s_installed_singers = new Vector<SingerConfig>();

        /// <summary>
        /// 指定したプログラムチェンジが担当する歌手の歌唱言語を表すインデクスを取得します
        /// </summary>
        /// <param name="program_change"></param>
        /// <returns></returns>
        public static VsqVoiceLanguage GetLanguage( int program_change ) {
            String name = GetOriginalSinger( program_change );
            switch ( name ) {
                case "Miku":
                case "Rin":
                case "Len":
                case "Rin_ACT2":
                case "Len_ACT2":
                case "Gackpoid":
                    return VsqVoiceLanguage.Japanese;
            }
            return VsqVoiceLanguage.Default;
        }


        /// <summary>
        /// 指定したプログラムチェンジが担当する歌手の、オリジナルの歌手名を取得します。
        /// </summary>
        /// <param name="program_change"></param>
        /// <returns></returns>
        public static String GetOriginalSinger( int program_change ) {
            if ( s_singer_configs == null ) {
                LoadSingerConfigs();
            }
            if ( s_singer_configs.ContainsKey( program_change ) ) {
                SingerConfig sc = GetSingerInfo( program_change );
                String voiceidstr = sc.VOICEIDSTR;
                foreach ( SingerConfig installed in s_installed_singers ) {
                    if ( installed.VOICEIDSTR == voiceidstr ) {
                        return installed.VOICENAME;
                    }
                }
            }
            return "";
        }


        /// <summary>
        /// 指定したプログラムチェンジが担当する歌手の情報を、VsqIDに変換した物を取得します
        /// </summary>
        /// <param name="program_change"></param>
        /// <returns></returns>
        public static VsqID GetSingerID( int program_change ) {
            VsqID ret = new VsqID( 0 );
            ret.type = VsqIDType.Singer;
            SingerConfig sc = GetSingerInfo( program_change );
            int language = 0;
            foreach ( SingerConfig sc2 in s_installed_singers ) {
                if ( sc.VOICEIDSTR == sc2.VOICEIDSTR ) {
                    switch ( sc2.VOICENAME ) {
                        case "Miku":
                            language = 0;
                            break;
                    }
                }
            }
            ret.IconHandle = new IconHandle();
            ret.IconHandle.IconID = "$0701" + program_change.ToString( "0000" );
            ret.IconHandle.IDS = sc.VOICENAME;
            ret.IconHandle.Index = 0;
            ret.IconHandle.Language = language;
            ret.IconHandle.Length = 1;
            ret.IconHandle.Original = sc.Original;
            ret.IconHandle.Program = program_change;
            ret.IconHandle.Type = VsqHandleType.Singer;
            ret.IconHandle.Caption = "";
            return ret;
        }


        /// <summary>
        /// 指定したプログラムチェンジの歌手情報を取得します。
        /// </summary>
        /// <param name="program_change"></param>
        /// <returns></returns>
        public static SingerConfig GetSingerInfo( int program_change ) {
            if ( s_singer_configs == null ) {
                LoadSingerConfigs();
            }
            if ( s_singer_configs.ContainsKey( program_change ) ) {
                return s_singer_configs[program_change];
            } else {
                return null;
            }
        }


        /// <summary>
        /// 歌手設定を読み込む。GetSingerInfo, GetSingerIDが最初に呼ばれた時に自動的に呼び出されるが、明示的に呼び出しても良い。
        /// </summary>
        public static void LoadSingerConfigs() {
            if ( s_singer_configs == null ) {
                LoadSingerConfigs( GetExpDbPath() );
            }
        }


        /// <summary>
        /// SingerEditorによって設定された歌手のリストを取得する．リストのキーは「プログラムチェンジ」の値に対応する
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        private static void LoadSingerConfigs( String path ) {
            s_singer_configs = new Dictionary<int, SingerConfig>();
            String map_file = Path.Combine( path, "voice.map" );
            if ( !File.Exists( map_file ) ) {
                return;
            }
            using ( FileStream fs = new FileStream( map_file, 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
                        String file = Path.Combine( path, "vvoice" + value + ".vvd" );
                        if ( File.Exists( file ) ) {
                            s_singer_configs.Add( i, new SingerConfig( file, (int)(value - 1) ) );
                        }
                    }
                }
            }
            Vector<String> voiceidstrs = new Vector<String>();
            foreach ( SingerConfig sc in s_singer_configs.Values ) {
                if ( !voiceidstrs.Contains( sc.VOICEIDSTR ) ) {
                    voiceidstrs.Add( sc.VOICEIDSTR );
                }
            }
            foreach ( String s in voiceidstrs ) {
                String dir = Path.Combine( path, s );
                String[] files = Directory.GetFiles( dir, "*.vvd" );
                foreach ( String s2 in files ) {
                    String file = Path.Combine( dir, s2 );
                    if ( File.Exists( file ) ) {
                        s_installed_singers.Add( new SingerConfig( file, -1 ) );
                    }
                }
            }
        }


        /// <summary>
        /// 長さ8のバイト列をリトルエンディアンとみなし、unsigned longに変換します
        /// </summary>
        /// <param name="oct"></param>
        /// <returns></returns>
        private static long 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];
        }


        /// <summary>
        /// VOCALOID2 VSTiのdllへのフルパスを取得します
        /// </summary>
        /// <returns></returns>
        public static unsafe String GetVstiDllPath() {
            if ( s_dll_path_done ) {
                return s_dll_path;
            }
            try {
                uint hKey;
                int ret = windows.RegOpenKeyExW( windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\VOCALOID2\\APPLICATION", 0, windows.KEY_READ, &hKey );
                if ( ret != windows.ERROR_SUCCESS ) {
                    ret = windows.RegOpenKeyExW( windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\VOCALOID2_DEMO\\APPLICATION", 0, windows.KEY_READ, &hKey );
                    if ( ret != windows.ERROR_SUCCESS ) {
                        return s_dll_path;
                    }
                }

                FILETIME ft;
                for ( uint i = 0; ; i++ ) {
                    String lpszName = new String( new char[64] );
                    String pClass = new String( new char[64] );
                    uint dwNameSize = (uint)lpszName.Length;
                    uint pcbClass = 64;
                    int lRes = windows.RegEnumKeyExW( hKey, i, lpszName, &dwNameSize, (uint*)0, pClass, &pcbClass, &ft );
                    if ( lRes != windows.ERROR_SUCCESS ) {
                        break;
                    }

                    uint hChildKey;
                    ret = windows.RegOpenKeyExW( hKey, lpszName, 0, windows.KEY_READ, &hChildKey );
                    if ( ret != windows.ERROR_SUCCESS ) {
                        continue;
                    }

                    uint dwType = windows.REG_SZ;
                    uint dwSize = windows.MAX_PATH;
                    byte[] tszVSTPlugin = new byte[windows.MAX_PATH];
                    tszVSTPlugin[0] = (byte)'\0';
                    fixed ( byte* pData = &tszVSTPlugin[0] ) {
                        ret = windows.RegQueryValueExW( hChildKey, "PATH", (uint*)0, &dwType, pData, &dwSize );
                    }
                    windows.RegCloseKey( hChildKey );
                    if ( ret != windows.ERROR_SUCCESS ) {
                        continue;
                    }

                    String name = Encoding.Unicode.GetString( tszVSTPlugin, 0, (int)dwSize );
                    if ( name.EndsWith( "\0" ) ) {
                        name = name.SubString( 0, name.Length - 1 );
                    }
                    // 製品版
                    if ( name.EndsWith( "\\vocaloid2.dll" ) ) {
                        s_dll_path = name;
                        break;
                    }
                    // デモ版
                    if ( name.EndsWith( "\\vocaloid2_demo.dll" ) ) {
                        s_dll_path = name;
                        break;
                    }
                }
                windows.RegCloseKey( hKey );
            } catch {
            } finally {
                s_dll_path_done = true;
            }
            return s_dll_path;
        }


        /// <summary>
        /// 歌唱データベースが保存されているディレクトリのフルパスを取得します
        /// </summary>
        /// <returns></returns>
        public static unsafe String GetExpDbPath() {
            if ( s_exp_db_dir_done ) {
                return s_exp_db_dir;
            }
            try {
                uint hKey;
                int ret = windows.RegOpenKeyExW( windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\VOCALOID2\\DATABASE\\VOICE", 0, windows.KEY_READ, &hKey );
                if ( ret != windows.ERROR_SUCCESS ) {
                    ret = windows.RegOpenKeyExW( windows.HKEY_LOCAL_MACHINE, "SOFTWARE\\VOCALOID2_DEMO\\DATABASE\\VOICE", 0, windows.KEY_READ, &hKey );
                    if ( ret != windows.ERROR_SUCCESS ) {
                        return s_exp_db_dir;
                    }
                }

                FILETIME ft;
                for ( uint i = 0; ; i++ ) {
                    String lpszName = new String( new char[64] );
                    String pClass = new String( new char[64] );
                    uint dwNameSize = (uint)lpszName.Length;
                    uint pcbClass = 64;
                    int lRes = windows.RegEnumKeyExW( hKey, i, lpszName, &dwNameSize, (uint*)0, pClass, &pcbClass, &ft );
                    if ( lRes != windows.ERROR_SUCCESS ) {
                        break;
                    }

                    uint hChildKey;
                    ret = windows.RegOpenKeyExW( hKey, lpszName, 0, windows.KEY_READ, &hChildKey );
                    if ( ret != windows.ERROR_SUCCESS ) {
                        continue;
                    }

                    uint dwType = windows.REG_SZ;
                    uint dwSize = windows.MAX_PATH;
                    byte[] tszVSTPlugin = new byte[windows.MAX_PATH];
                    tszVSTPlugin[0] = (byte)'\0';
                    fixed ( byte* pData = &tszVSTPlugin[0] ) {
                        ret = windows.RegQueryValueExW( hChildKey, "INSTALLDIR", (uint*)0, &dwType, pData, &dwSize );
                    }
                    windows.RegCloseKey( hChildKey );
                    if ( ret != windows.ERROR_SUCCESS ) {
                        continue;
                    }

                    String name = Encoding.Unicode.GetString( tszVSTPlugin, 0, (int)dwSize );
                    if ( name.EndsWith( "\0" ) ) {
                        name = name.SubString( 0, name.Length - 1 );
                    }
                    if ( name.EndsWith( "\\voicedbdir" ) ) {
                        s_exp_db_dir = name;
                        break;
                    }
                }
                windows.RegCloseKey( hKey );
            } catch {
            } finally {
                s_exp_db_dir_done = true;
            }
            return s_exp_db_dir;
        }
    }
