# -*- coding: utf-8 -*-
# Moovida - Home multimedia server
# Copyright (C) 2006-2009 Fluendo Embedded S.L. (www.fluendo.com).
# All rights reserved.
#
# This file is available under one of two license agreements.
#
# This file is licensed under the GPL version 3.
# See "LICENSE.GPL" in the root of this distribution including a special
# exception to use Moovida with Fluendo's plugins.
#
# The GPL part of Moovida is also available under a commercial licensing
# agreement from Fluendo.
# See "LICENSE.Moovida" in the root directory of this distribution package
# for details on that license.

"""
Module responsible for managing Elisa config file upgrades
"""

__maintainer__ = 'Philippe Normand <philippe@fluendo.com>'

from elisa.core import log, config, media_uri
import os, datetime
from distutils.version import LooseVersion

class ConfigUpgrader(log.Loggable):

    def __init__(self, current_config, default_config):
        log.Loggable.__init__(self)
        self._current_config = current_config
        self._default_config = default_config
        self._new_config_fname = 'elisa_new.conf'
        
        today = datetime.date.today().isoformat()
        if self._current_config:

            self._today = self._current_config.get_option('install_date',
                                                          section='general',
                                                          default=today)
            self._current_version = current_config.get_option('version',
                                                              section='general',
                                                              default='0.1.7')
        else:
            self._today = today
            self._current_version = '0.1.7'
            
        self.releases = [(('0.1.7', ), self._update_0_1_to_0_3_1),
                         (('0.3.1', '0.3.1.1', ), self._update_0_3_1_to_0_3_2),
                         (('0.3.2', '0.3.2.1', ), self._update_0_3_2_to_0_3_3),
                         (('0.3.3', '0.3.3.1', '0.3.3.2',
                           '0.3.4.rc1', '0.3.4.rc2',),
                          self._update_0_3_3_to_0_3_4),
                         (('0.3.4', '0.3.4.1'), self._update_0_3_4_to_0_3_5),
                         (('0.5.7', '0.5.8'), self._update_0_5_7_to_0_5_8),
                         (('0.5.8', '0.5.9'), self._update_0_5_8_to_0_5_9),
                         (('0.5.14', '0.5.15'), self._update_0_5_14_to_0_5_15),
                        ]

    def _get_version_index_upgrade(self, version_str):
        idx = 0
        found_idx = 0
        to_call = None
        for versions, upgrade_call in self.releases:
            if version_str == versions[0]:
                found_idx = idx
                to_call = upgrade_call
                break
            idx += 1
        return (found_idx, to_call)
    
    def update_for(self, version):
        cfg = self._current_config
        version_str = '.'.join([str(i) for i in version])
        loose_version = LooseVersion(version_str)
        updated = False
        self.info("Trying to upgrade from %s to %s", self._current_version,
                  version_str)

        # incrementally upgrade the config throught the releases history to
        # requested version
        idx, call = self._get_version_index_upgrade(self._current_version)
        while idx < len(self.releases) and call != None:

            if LooseVersion(self._current_version) >= loose_version:
                break

            cfg = call(cfg)
            updated = True

            # 0.3.4.rc* is considered superior to 0.3.4. So follows a
            # workaround to avoid an infinite loop
            if self._current_version == '0.3.4':
               if loose_version < LooseVersion('0.3.5'):
                   break
               else:
                   cfg = self._update_0_3_4_to_0_3_5(cfg)

            idx, call = self._get_version_index_upgrade(self._current_version)

        if updated:
            cfg.set_option('install_date', self._today, section='general')
            cfg.set_option('version', version_str, section='general')

            full_path = self._current_config.get_filename()
            cfg.set_filename(full_path)
            if os.path.exists(self._new_config_fname):
                os.unlink(self._new_config_fname)
            self.info("Updated config to %s", cfg.get_filename())
        else:
            self.info("No update needed")
        return cfg

    def _extend_list(self, items_list, items_to_add):
        for item in items_to_add:
            if item not in items_list:
                items_list.append(item)
        return items_list

    def _backup(self, version=None, cfg=None):
        if version is None:
            if self._current_version is None:
                version = (0, 1, 7)
            else:
                version = str(self._current_version).split('.')
        
        version = '.'.join([str(i) for i in version])

        if not cfg:
            cfg = self._current_config
            
        full_path = os.path.join(cfg.get_config_dir(),
                                 'elisa_%s.bak' % version)
        self.debug("Doing a backup to the old config on %s", full_path)
        cfg.write(full_path)

    def _update_0_5_14_to_0_5_15(self, cfg=None):
        self.info('Upgrading from 0.5.14 to 0.5.15')
        self._backup((0, 5, 14), cfg)
        if not cfg:
            cfg = self._current_config

        # In 0.5.15, the *_providers keys have been removed from the
        # configuration. Discovery of the providers is now done using the
        # plugins' entry points.
        provider_entries = ['resource_providers', 'metadata_providers',
                            'service_providers', 'input_providers']
        for providers_entry in provider_entries:
            try:
                cfg.del_option(providers_entry, section='general')
            except config.SectionNotFound:
                pass

        self._current_version = '0.5.15'
        return cfg

    def _update_0_5_8_to_0_5_9(self, cfg=None):
        self.info('Upgrading from 0.5.8 to 0.5.9')
        self._backup((0, 5, 8), cfg)
        if not cfg:
            cfg = self._current_config

        resource_providers = cfg.get_option('resource_providers',
                                            section='general', default=[])
        new_providers = ['search.search_metaresource_provider:SearchMetaresourceProvider',
                         'yesfm.yesfm_resource:YesfmResource']
        for provider in new_providers:
            if provider not in resource_providers:
                resource_providers = self._extend_list(resource_providers, 
                                                       [provider])

        self._current_version = '0.5.9'
        return cfg

    def _update_0_5_7_to_0_5_8(self, cfg=None):
        self.info('Upgrading from 0.5.7 to 0.5.8')
        self._backup((0, 5, 7), cfg)
        if not cfg:
            cfg = self._current_config

        service_providers = cfg.get_option('service_providers',
                                           section='general', default=[])
        new_service = 'database.dbus_service:DatabaseDBusServiceProvider'
        if new_service not in service_providers:
            service_providers = self._extend_list(service_providers, 
                                                  [new_service])

        self._current_version = '0.5.8'
        return cfg

    def _update_0_3_4_to_0_3_5(self, cfg=None):
        self.info('Upgrading from 0.3.4 to 0.3.5')
        self._backup((0, 3, 4), cfg)
        if not cfg:
            cfg = self._current_config

        service_providers = cfg.get_option('service_providers',
                                           section='general', default=[])
        if 'updater:updater_service' not in service_providers:
            service_providers = self._extend_list(service_providers, 
                                                  ['updater:updater_service'])

        # rename some sections...
        sections_to_rename = {'base:player_controller': 'player:player_controller',
                              'poblenou:player_view': 'player:playback_view'
                              }
        for old_name, new_name in sections_to_rename.iteritems():
            cfg.rename_section(old_name, new_name)

        # move visualisation from player_view to player_controller
        visu = cfg.get_option('visualisation', section='player:playback_view')
        if visu:
            cfg.del_option('visualisation', section='player:playback_view')
            cfg.set_option('visualisation', visu,
                           section='player:player_controller')
            
        self._current_version = '0.3.5'
        return cfg

    def _update_0_3_3_to_0_3_4(self, cfg=None):
        self.info('Upgrading from 0.3.3 to 0.3.4')
        self._backup((0, 3, 3), cfg)
        if not cfg:
            cfg = self._current_config

        # remove some sections
        for section_name in ['base:dvd_player_controller',
                             'poblenou:dvd_player_view',
                             'dvd:dvd_activity']:
            cfg.del_section(section_name)
                             

        # remove stage6 media_provider
        media_providers = cfg.get_option('media_providers',
                                         section='general', default=[])
        if 'stage6:stage_media' in media_providers:
            media_providers.remove('stage6:stage_media')
        cfg.set_option('media_providers', media_providers,
                       section='general')

        # add missing metadata_providers
        metadata_providers = cfg.get_option('metadata_providers',
                                            section='general', default=[])
        metadata_providers = self._extend_list(metadata_providers,
                                               ['media_db:db_metadata',])
        cfg.set_option('metadata_providers', metadata_providers,
                       section='general')

        # add missing service_providers
        service_providers = cfg.get_option('service_providers',
                                           section='general', default=[])
        service_providers = self._extend_list(service_providers,
                                              ['media_db:media_scanner',
                                               'osso:osso_service'])

        # remove the updater service
        if 'updater:updater_service' in service_providers:
            service_providers.remove('updater:updater_service')
        
        cfg.set_option('service_providers', service_providers,
                       section='general')

        # rename some sections...
        sections_to_rename = {'media_scanner': 'media_db:media_scanner',
                              }
        for old_name, new_name in sections_to_rename.iteritems():
            cfg.rename_section(old_name, new_name)

        # move some options from media_db:media_scanner to media_db:db
        for opt_name in ('db_backend', 'database'):
            value = cfg.get_option(opt_name, section='media_db:media_scanner')
            cfg.set_option(opt_name, value, section='media_db:db')
            cfg.del_option(opt_name, section='media_db:media_scanner')

        # check we don't display fps on the viewport
        show_fps = cfg.get_option('show_fps', section='pigment:pigment_context')
        if show_fps == '1':
            cfg.set_option('show_fps', '0', section='pigment:pigment_context')

        # transfer 'start_fullscreen' option from raval:elisa_controller
        # section to pigment:pigment_context section
        start_fullscreen = cfg.get_option('start_fullscreen',
                                          section='raval:elisa_controller')
        cfg.set_option('start_fullscreen', start_fullscreen,
                       section='pigment:pigment_context')
        cfg.del_option('start_fullscreen',
                       section='raval:elisa_controller')

        self._current_version = '0.3.4'
        return cfg


    def _update_0_3_2_to_0_3_3(self, cfg=None):
        self.info('Upgrading from 0.3.2 to 0.3.3')
        self._backup((0, 3, 2), cfg)
        if not cfg:
            cfg = self._current_config

        # copy media locations
        paths = {}
        paths['audio'] = cfg.get_option('locations',
                                        section='base:audio_activity',
                                        default=[])
        paths['video'] = cfg.get_option('locations',
                                        section='base:video_activity',
                                        default=[])
        paths['image'] = cfg.get_option('locations',
                                        section='base:image_activity',
                                        default=[])
        all_locations =  []
        for media_type, locations in paths.iteritems():
            for location in locations:
                cfg.set_option('only_media', [media_type,],
                                      section=location)
                if location not in all_locations:
                    all_locations.append(location)
                    
        cfg.set_option('locations', locations,
                       section='xmlmenu:locations_builder')

        def rename(optname, mapping, section='general'):
            contents = cfg.get_option(optname, section=section)
            for old_name, new_name in mapping.iteritems():
                if old_name in contents:
                    contents.remove(old_name)
                    contents.append(new_name)
                cfg.rename_section(old_name, new_name)

        # rename media_providers
        mp_map = {'coherence_plugin:upnp_media': 'coherence:upnp_media',
                  'media_bad:ipod_media': 'ipod:ipod_media',
                  'media_bad:daap_media': 'daap:daap_media',
                  'media_bad:mmslist_media': 'webtv:mmslist_media',
                  'media_good:elisa_media': 'media_db:elisa_media',
                  'media_good:gnomevfs_media': 'gvfs:gnomevfs_media',
                  'media_ugly:podcatcher_media': 'aggregator:podcatcher_media',
                  'media_ugly:shoutcast_media': 'shoutcast:shoutcast_media',
                  }
        rename('media_providers', mp_map)

        # rename metadata_providers
        md_map = {'media_good:amazon_covers': 'amazon:amazon_covers',
                  'media_good:cover_cache': 'album_art:cover_cache',
                  'media_good:gst_metadata': 'gstreamer:gst_metadata_client',
                  'media_good:cover_in_dir': 'album_art:cover_in_dir',
                  }
        rename('metadata_providers', md_map)

        # rename service_providers
        service_map = {'coherence_plugin:upnp_media_server': 'coherence:upnp_media_server',
                       'coherence_plugin:coherence_service': 'coherence:coherence_service',
                       'services_good:lastfm_scrobbler': 'lastfm:lastfm_scrobbler',
                       'services_good:twisted_pb': 'twisted:twisted_pb',
                       'services_good:http_server': 'httpd:http_server',
                       }
        rename('service_providers', service_map)

        # rename input_providers
        ip_map = {'input_good:lirc_input': 'lirc:lirc_input',
                  'input_good:raw_input': 'base:raw_input',
                  'input_bad:bluetooth_input': 'bluetooth:bluetooth_input',
                  'input_bad:webcam_input': 'webcam:webcam_input',
                  }
        backends = cfg.get_option('backends', section='general', default=[])
        frontends = cfg.get_option('frontends', section='general', default=[])

        for section_name in backends + frontends:
            rename('input_providers', ip_map, section=section_name)

        # add missing default media_providers
        media_providers = cfg.get_option('media_providers', section='general',
                                         default=[])
        media_providers = self._extend_list(media_providers,
                                            ['daap:daap_media',
                                             'youtube:youtube_media',])
        cfg.set_option('media_providers', media_providers, section='general')

        # add missing metadata_providers
        metadata_providers = cfg.get_option('metadata_providers',
                                            section='general', default=[])
        if 'media_good:taglib_metadata' in metadata_providers:
            metadata_providers.remove('media_good:taglib_metadata')
        metadata_providers = self._extend_list(metadata_providers,
                                               ['gstreamer:gst_metadata_client',
                                                'album_art:cover_in_dir',
                                                'amazon:amazon_covers'])
        cfg.set_option('metadata_providers', metadata_providers,
                       section='general')

        # add missing service_providers
        service_providers = cfg.get_option('service_providers',
                                           section='general', default=[])
        service_providers = self._extend_list(service_providers,
                                              ['gnome:gnome_screensaver_service',])
        cfg.set_option('service_providers', service_providers,
                       section='general')

        # add missing themes
        themes = ['raval:tango_theme', 'raval:poblenou_theme',
                  'raval:chris_theme']
        cfg_themes = cfg.get_option('themes',
                                    section='theme_switcher:theme_switcher_activity',
                                    default=[])
        cfg_themes = self._extend_list(cfg_themes,themes)
        cfg.set_option('themes',cfg_themes,
                       section='theme_switcher:theme_switcher_activity')

        # fix backend config
        backend_section = cfg.get_section('backend1')
        backend_section['activity'] = 'raval:elisa_activity'
        backend_section['controller'] = 'raval:elisa_controller'
        backend_section['mvc_mappings'] = 'raval:data/raval_mvc_mappings.conf'
        cfg.set_section('backend1', backend_section)

        # fix frontend config
        frontend_section = cfg.get_section('frontend1')
        frontend_section['theme'] = 'raval:tango_theme'
        cfg.set_section('frontend1', frontend_section)

        # rename some sections...
        sections_to_rename = {'poblenou:elisa_controller': 'raval:elisa_controller',
                              'base:player_controller': 'raval:player_controller'
                              }
        for old_name, new_name in sections_to_rename.iteritems():
            cfg.rename_section(old_name, new_name)

        # remove sections not used anymore
        sections_to_remove = ['base:main_menu_activity',
                              'base:audio_activity', 'base:video_activity',
                              'base:image_activity', 'elisa_view']
        for path in sections_to_remove:
            cfg.del_section(path)
        
        self._current_version = '0.3.3'
        return cfg
        
    def _update_0_3_1_to_0_3_2(self, cfg=None):
        self.info('Upgrading from 0.3.1 to 0.3.2')
        self._backup((0, 3, 1), cfg)
        if not cfg:
            cfg = self._current_config

        def upgrade_paths(opt_name, mapping, section='general'):
            
            old_option = cfg.get_option(opt_name,section=section)
            for old_path, new_path in mapping.iteritems():
                if old_path in old_option:
                    old_option.remove(old_path)
                    old_option.append(new_path)

                old_path_section = cfg.get_section(old_path)
                if old_path_section:
                    cfg.set_section(new_path, old_path_section)
                    cfg.del_section(old_path)
                    
            cfg.set_option(opt_name, old_option, section=section)
            
        # upgrade media_providers paths
        media_providers = {'base:upnp_media': 'coherence_plugin:upnp_media',
                           'base:elisa_media': 'media_good:elisa_media',
                           'base:gnomevfs_media': 'media_good:gnomevfs_media',
                           'base:ipod_media': 'media_bad:ipod_media',
                           'base:mmslist_media': 'media_good:mmslist_media',
                           'base:shoutcast_media': 'media_ugly:shoutcast_media',
                           'base:daap_media': 'media_bad:daap_media',
                           'base:audiocd': 'audiocd:audiocd_media'}
        upgrade_paths('media_providers', media_providers)

        # new media_providers
        new_media_providers = ['fspot:fspot_media','stage6:stage_media',
                               'media_ugly:shoutcast_media',
                               'flickr:flickr_media']
        media_providers = cfg.get_option('media_providers', section='general')
        for mp in new_media_providers:
            if mp not in media_providers:
                media_providers.append(mp)
        cfg.set_option('media_providers', media_providers)

        # upgrade metadata_providers paths
        metadata_providers = {'base:gst_metadata':'media_good:gst_metadata',
                              'base:taglib_metadata': 'media_good:taglib_metadata',
                              'base:cover_in_dir':'media_good:cover_in_dir',
                              'base:cover_cache': 'media_good:cover_cache',
                              'base:amazon_covers':'media_good:amazon_covers'}
        upgrade_paths('metadata_providers', metadata_providers)
        
        # upgrade service_providers paths
        service_providers = {'lastfm:audioscrobbler_service':'services_good:lastfm_scrobbler',
                             'base:hal_service':'hal:hal_service',
                             'base:twisted_pb':'services_good:twisted_pb',
                             'base:http_server':'services_good:http_server',
                             'base:coherence_service':'coherence_plugin:coherence_service',
                             'base:upnp_media_server':'coherence_plugin:upnp_media_server',
                             'base:upnp_media_renderer':'coherence_plugin:upnp_media_renderer'
                             }
        upgrade_paths('service_providers', service_providers)

        # new service_providers
        new_service_providers = ['coherence_plugin:coherence_service',
                                 ]
        service_providers = cfg.get_option('service_providers',
                                           section='general')
        for sp in new_service_providers:
            if sp not in service_providers:
                service_providers.append(sp)
        cfg.set_option('service_providers', service_providers)

        # upgrade input_providers paths
        input_providers = {'base:lirc_input':'input_good:lirc_input',
                           'base:raw_input': 'input_good:raw_input',
                           'base:bluetooth_input':'input_bad:bluetooth_input'}
        upgrade_paths('input_providers', input_providers, 'backend1')
        upgrade_paths('input_providers', input_providers, 'frontend1')

        # upgrade main menu
        menu_activities = ['base:audio_activity', 'base:video_activity',
                           'base:image_activity', 'base:config_activity',
                           'base:service_activity']
        cfg.set_option('menu_activities', menu_activities,
                       section='base:main_menu_activity')

        # upgrade services menu
        service_activities = ['service:about_activity']
        cfg.set_option('service_activities', service_activities,
                       section='base:service_activity')
        cfg.del_section('service:service_activity')

        # upgrade player and dvd controller
        cfg.del_section('base:player_controller')
        cfg.del_section('dvd:dvd_player_controller')
        
        cfg.set_option('version', '0.3.2', section='general')
        self._current_version = '0.3.2'
        return cfg
            
    def _update_0_1_to_0_3_1(self, cfg=None):
        self.info('Upgrading from 0.1.x to 0.3.1')
        self._backup()
        cfg = config.Config(self._new_config_fname, self._default_config)
        
        # media locations options have migrated to activities
        activity_map = {'plugins.music': 'base:audio_activity',
                        'plugins.movies': 'base:video_activity',
                        'plugins.pictures': 'base:image_activity'
                        }
        for old_section, new_section in activity_map.iteritems():
            locations = self._current_config.get_option('locations',
                                                        section=old_section,
                                                        default=[])
            new_locations = []
            location_labels = {}
            for location in locations:

                # meta:// URIs not supported anymore
                if location.startswith('meta'):
                    continue

                # FIXME: location/* not supported yet
                if location.endswith('*'):
                    location = location[:-1]

                location = location.decode('utf-8')
                location_uri = media_uri.MediaUri(location)

                # label parameter removed
                label = location_uri.get_param('label')
                if label:
                    location_uri.del_param('label')
                    location_labels[str(location_uri)] = label
                    
                new_locations.append(str(location_uri))
                
            cfg.set_option('locations', new_locations, section=new_section)
            activity_section = cfg.get_section(new_section)
            for location, label in location_labels.iteritems():
                activity_section[location] = {'label': str(label)}

        # start_fullscreen option moved to elisa_controller
        start_fullscreen = self._current_config.get_option('start_fullscreen',
                                                           default='0')
        controller_section = cfg.get_section('poblenou:elisa_controller')
        controller_section['start_fullscreen'] = str(start_fullscreen)

        # media_manager
        media_manager_opts = self._current_config.get_section('media_manager')
        media_scanner_section = cfg.get_section('media_scanner')
        try:
            media_scanner_section['enabled'] = str(media_manager_opts['enable_cache'])
        except KeyError:
            self.warning("enable_cache option not found in old config")

        try:
            media_scanner_section['database'] = media_manager_opts['db_name']
        except KeyError:
            self.warning("db_name option not found in old config")
            
        for timebased in ('fivemin', 'hourly', 'daily', 'weekly'):
            opt_name = '%s_location_updates' % timebased
            try:
                media_scanner_section[opt_name] = media_manager_opts[opt_name]
            except KeyError:
                self.warning("%s option not found in old config", opt_name)
                continue
            
        self._current_version = '0.3.1'
        return cfg
