/*
 * VsqUtil.cs
 * Copyright (c) 2008-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.Text;
using System.Collections.Generic;

using bocoree;

namespace Boare.Lib.Vsq {

    /// <summary>
    /// Represents the voice language of singer.
    /// </summary>
    public enum VsqVoiceLanguage : int {
        /// <summary>
        /// Default value, equivalent to "Japanese".
        /// </summary>
        Default = 0,
        /// <summary>
        /// Japanese
        /// </summary>
        Japanese = 0,
        /// <summary>
        /// English
        /// </summary>
        English = 1,
    }

    /// <summary>
    /// Static library to treat the information of VOCALOID2 system.
    /// </summary>
    public static class VsqUtil {
        /// <summary>
        /// Maximum number of singers which can be allowed in VOCALOID2 system.
        /// </summary>
        public const uint MAX_SINGERS = 0x4000;

        private static string s_dll2_path = "";
        private static string s_dll1_path = "";
        private static string s_exp_db_dir = "";
        private static bool s_initialized = false;
        private static Dictionary<int, SingerConfig> s_singer_configs = null;
        private static List<SingerConfig> s_installed_singers = new List<SingerConfig>();
        private static string s_editor_path = "";

        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>
        /// Gets the list of installed singers which is installed in the computer.
        /// </summary>
        /// <returns></returns>
        public static SingerConfig[] getInstalledSingers() {
            return s_installed_singers.ToArray();
        }

        /// <summary>
        /// Gets the list of singer configs.
        /// </summary>
        /// <returns></returns>
        public static Dictionary<int, SingerConfig> getSingerConfigs() {
            Dictionary<int, SingerConfig> ret = new Dictionary<int, SingerConfig>();
            foreach ( KeyValuePair<int, SingerConfig> kvp in s_singer_configs ) {
                ret.Add( kvp.Key, kvp.Value );
            }
            return ret;
        }

        /// <summary>
        /// Gets the path of VOCALOID Editor.
        /// </summary>
        public static string getEditorPath() {
            if ( !s_initialized ) {
                init();
            }
            return s_editor_path;
        }

        /// <summary>
        /// Gets the voice language of specified program change
        /// </summary>
        /// <param name="program_change">program change to get the voice language</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":
                case "Luka_JPN":
                    return VsqVoiceLanguage.Japanese;
                case "Sweet_Ann":
                case "Prima":
                case "Luka_ENG":
                    return VsqVoiceLanguage.English;
            }
            return VsqVoiceLanguage.Default;
        }

        /// <summary>
        /// Gets the name of original singer of specified program change.
        /// </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>
        /// Gets the VsqID of program change.
        /// </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 lang = 0;
            foreach ( SingerConfig sc2 in s_installed_singers ) {
                if ( sc.VOICEIDSTR == sc2.VOICEIDSTR ) {
                    switch ( sc2.VOICENAME ) {
                        case "Miku":
                            lang = 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 = lang;
            ret.IconHandle.Length = 1;
            ret.IconHandle.Original = sc.Original;
            ret.IconHandle.Program = program_change;
            ret.IconHandle.Caption = "";
            return ret;
        }

        /// <summary>
        /// Gets the singer information of pecified program change.
        /// </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>
        /// Loads the singer config from ExpDbPath.
        /// This method will automatically called when GetSingerInfo method or GetSingerID method is called in first time.
        /// </summary>
        public static void loadSingerConfigs() {
            if ( s_singer_configs == null ) {
                loadSingerConfigs( getExpDbPath() );
            }
        }

        /// <summary>
        /// Loads the list of singer configuration from the specified path.
        /// Keys in the dictionary "s_singer_configs" represents the program change of each singer.
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        private static void loadSingerConfigs( string path ) {
#if DEBUG
            bocoree.debug.push_log( "SingerConfig+LoadSingerConfigs(String)" );
#endif
            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 ) {
                        string file = Path.Combine( path, "vvoice" + value + ".vvd" );
                        if ( File.Exists( file ) ) {
                            s_singer_configs.Add( i, new SingerConfig( file, (int)(value - 1) ) );
                        }
                    }
                }
            }
            List<string> voiceidstrs = new List<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 ) );
                    }
                }
            }
#if DEBUG
            bocoree.debug.push_log( "    s_singer_configs" );
            foreach ( SingerConfig sc in s_singer_configs.Values ) {
                bocoree.debug.push_log( "        VOICENAME=" + sc.VOICENAME );
            }
            bocoree.debug.push_log( "    s_installed_singers" );
            foreach( SingerConfig sc in s_installed_singers ){
                bocoree.debug.push_log( "        VOICENAME=" + sc.VOICENAME );
            }
#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>
        private 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];
        }

        /// <summary>
        /// Gets the path of VOCALOID2 Playback VSTi dll.
        /// </summary>
        /// <returns></returns>
        public static string getDllPathVsti2() {
            if ( !s_initialized ) {
                init();
            }
            return s_dll2_path;
        }

        /// <summary>
        /// Gets the path of VOCALOID1 Playback VSTi dll.
        /// </summary>
        /// <returns></returns>
        public static string getDllPathVsti1() {
            if ( !s_initialized ) {
                init();
            }
            return s_dll1_path;
        }

        /// <summary>
        /// Initializes the library.
        /// </summary>
        private static void init() {
            try {
#if DEBUG
                bocoree.debug.push_log( "VsqUtil+Init()" );
#endif

                // vocaloid2 dll path
                Microsoft.Win32.RegistryKey regkey_dll = null;
                regkey_dll = Microsoft.Win32.Registry.LocalMachine.OpenSubKey( "SOFTWARE\\VOCALOID2\\APPLICATION", false );
                if ( regkey_dll == null ) {
                    regkey_dll = Microsoft.Win32.Registry.LocalMachine.OpenSubKey( "SOFTWARE\\VOCALOID2_DEMO\\APPLICATION", false );
                }
                if ( regkey_dll != null ) {
                    string[] keys = regkey_dll.GetSubKeyNames();
                    for ( int i = 0; i < keys.Length; i++ ) {
                        Microsoft.Win32.RegistryKey key = regkey_dll.OpenSubKey( keys[i], false );
                        if ( key != null ) {
                            string name = (string)key.GetValue( "PATH" );
                            if ( name.ToLower().EndsWith( "\\vocaloid2.dll" ) ) {
                                s_dll2_path = name;
                            } else if ( name.ToLower().EndsWith( "\\vocaloid2_demo.dll" ) ) {
                                s_dll2_path = name;
                            } else if ( name.ToLower().EndsWith( "\\vocaloid2.exe" ) ) {
                                s_editor_path = name;
                            }
                            key.Close();
                        }
                    }
                    regkey_dll.Close();
                }
                
                // vocaloid1 dll path
                Microsoft.Win32.RegistryKey regkey_dll1 = null;
                regkey_dll1 = Microsoft.Win32.Registry.LocalMachine.OpenSubKey( "SOFTWARE\\VOCALOID\\APPLICATION", false );
                if ( regkey_dll1 != null ){
                    string[] keys = regkey_dll1.GetSubKeyNames();
                    for ( int i = 0; i < keys.Length; i++ ) {
                        Microsoft.Win32.RegistryKey key = regkey_dll1.OpenSubKey( keys[i], false );
                        if ( key != null ) {
                            string name = (string)key.GetValue( "PATH" );
                            if ( name.ToLower().EndsWith( "\\vocaloid.dll" ) ) {
                                s_dll1_path = name;
                                key.Close();
                                break;
                            }
                            key.Close();
                        }
                    }
                    regkey_dll1.Close();
                }

                // voicedbdir
                Microsoft.Win32.RegistryKey regkey_voicedb = Microsoft.Win32.Registry.LocalMachine.OpenSubKey( "SOFTWARE\\VOCALOID2\\DATABASE\\VOICE", false );
                if ( regkey_voicedb == null ) {
                    regkey_voicedb = Microsoft.Win32.Registry.LocalMachine.OpenSubKey( "SOFTWARE\\VOCALOID2_DEMO\\DATABASE\\VOICE", false );
                }
                if ( regkey_voicedb != null ) {

                    string[] keys = regkey_voicedb.GetSubKeyNames();
                    for ( int i = 0; i < keys.Length; i++ ) {
                        Microsoft.Win32.RegistryKey key = regkey_voicedb.OpenSubKey( keys[i], false );
                        if ( key != null ) {
                            string name = (string)key.GetValue( "INSTALLDIR" );
                            if ( name.ToLower().EndsWith( "\\voicedbdir" ) ) {
                                s_exp_db_dir = name;
                                break;
                            }
                            key.Close();
                        }
                    }
                    regkey_voicedb.Close();
                }

            } catch ( Exception ex ) {
                bocoree.debug.push_log( "VsqUtil.Init" );
                bocoree.debug.push_log( "    ex=" + ex );
            } finally {
                s_initialized = true;
#if DEBUG
                bocoree.debug.push_log( "    s_dll2_path=" + s_dll2_path );
                bocoree.debug.push_log( "    s_exp_db_dir=" + s_exp_db_dir );
#endif
            }
        }

        /// <summary>
        /// Gets the path of directories in which singer expression database is installed.
        /// </summary>
        /// <returns></returns>
        public static string getExpDbPath() {
            if ( !s_initialized ) {
                init();
            }
            return s_exp_db_dir;
        }
    }

}
