
function Exports(){
    /*************************************************
     * Variables
     *************************************************/
    this.availableCountries = {
        'DZ' : {name : 'Algeria', primaryLanguage : 'ar'},
        'AO' : {name : 'Angola', primaryLanguage : 'pt'},
        'AR' : {name : 'Argentina', primaryLanguage : 'es'},
        'AU' : {name : 'Australia', primaryLanguage : 'en'},
        'AT' : {name : 'Austria', primaryLanguage : 'de'},
        'AZ' : {name : 'Azerbaijan', primaryLanguage : 'en'},
        'BH' : {name : 'Bahrain', primaryLanguage : 'ar'},
        'BD' : {name : 'Bangladesh', primaryLanguage : 'en'},
        'BB' : {name : 'Barbados', primaryLanguage : 'en'},
        'BE' : {name : 'Belgium', primaryLanguage : 'nl'},
        'BA' : {name : 'Bosnia and Herzegovina', primaryLanguage : 'en'},
        'BR' : {name : 'Brazil', primaryLanguage : 'pt-PT'}, // cannot translate into pt-BR
        'BN' : {name : 'Brunei', primaryLanguage : 'en'},
        'BG' : {name : 'Bulgaria', primaryLanguage : 'bg'},
        'CA' : {name : 'Canada', primaryLanguage : 'en'},
        'CL' : {name : 'Chile', primaryLanguage : 'es'},
        'CN' : {name : 'China', primaryLanguage : 'zh-CN'},
        'CO' : {name : 'Colombia', primaryLanguage : 'es'},
        'CR' : {name : 'Costa Rica', primaryLanguage : 'es'},
        'HR' : {name : 'Croatia', primaryLanguage : 'hr'},
        'CY' : {name : 'Cyprus', primaryLanguage : 'el'},
        'CZ' : {name : 'Czech Republic', primaryLanguage : 'cs'},
        'DK' : {name : 'Denmark', primaryLanguage : 'da'},
        'DO' : {name : 'Dominican Republic', primaryLanguage : 'es'},
        'EC' : {name : 'Ecuador', primaryLanguage : 'es'},
        'EG' : {name : 'Egypt', primaryLanguage : 'ar'},
        'EE' : {name : 'Estonia', primaryLanguage : 'et'},
        'ET' : {name : 'Ethiopia', primaryLanguage : 'en'},
        'FO' : {name : 'Faroe Islands', primaryLanguage : 'en'},
        'FI' : {name : 'Finland', primaryLanguage : 'fi'},
        'FR' : {name : 'France', primaryLanguage : 'fr'},
        'DE' : {name : 'Germany', primaryLanguage : 'de'},
        'GH' : {name : 'Ghana', primaryLanguage : 'en'},
        'GR' : {name : 'Greece', primaryLanguage : 'el'},
        'GL' : {name : 'Greenland', primaryLanguage : 'en'},
        'HK' : {name : 'Hong Kong (SAR)', primaryLanguage : 'en'},
        'HU' : {name : 'Hungary', primaryLanguage : 'hu'},
        'IS' : {name : 'Iceland', primaryLanguage : 'is'},
        'IN' : {name : 'India', primaryLanguage : 'hi'},
        'ID' : {name : 'Indonesia', primaryLanguage : 'id'},
        'IQ' : {name : 'Iraq', primaryLanguage : 'en'},
        'IE' : {name : 'Ireland', primaryLanguage : 'en'},
        'IL' : {name : 'Israel', primaryLanguage : 'iw'},
        'IT' : {name : 'Italy', primaryLanguage : 'it'},
        'JM' : {name : 'Jamaica', primaryLanguage : 'en'},
        'JO' : {name : 'Jordan', primaryLanguage : 'ja'},
        'JP' : {name : 'Japan', primaryLanguage : 'ja'},
        'KZ' : {name : 'Kazakhstan', primaryLanguage : 'en'},
        'KE' : {name : 'Kenya', primaryLanguage : 'en'},
        'KR' : {name : 'Korea (South)', primaryLanguage : 'ko'},
        'KW' : {name : 'Kuwait', primaryLanguage : 'ar'},
        'LV' : {name : 'Latvia', primaryLanguage : 'lv'},
        'LB' : {name : 'Lebanon', primaryLanguage : 'ar'},
        'LY' : {name : 'Libya', primaryLanguage : 'ar'},
        'LI' : {name : 'Liechtenstein', primaryLanguage : 'de'},
        'LT' : {name : 'Lithuania', primaryLanguage : 'lt'},
        'LU' : {name : 'Luxembourg', primaryLanguage : 'en'},
        'MO' : {name : 'Macau', primaryLanguage : 'en'},
        'MY' : {name : 'Malaysia', primaryLanguage : 'en'},
        'MX' : {name : 'Mexico', primaryLanguage : 'es'},
        'MC' : {name : 'Monaco', primaryLanguage : 'fr'},
        'MA' : {name : 'Morocco', primaryLanguage : 'ar'},
        'MZ' : {name : 'Mozambique', primaryLanguage : 'pt'},
        'NL' : {name : 'Netherlands', primaryLanguage : 'nl'},
        'NZ' : {name : 'New Zealand', primaryLanguage : 'en'},
        'NG' : {name : 'Nigeria', primaryLanguage : 'en'},
        'NO' : {name : 'Norway', primaryLanguage : 'no'},
        'PS' : {name : 'Palestinian Territories', primaryLanguage : 'en'},
        'OM' : {name : 'Oman', primaryLanguage : 'ar'},
        'PK' : {name : 'Pakistan', primaryLanguage : 'ur'},
        'PA' : {name : 'Panama', primaryLanguage : 'es'},
        'PE' : {name : 'Peru', primaryLanguage : 'es'},
        'PH' : {name : 'Philippines', primaryLanguage : 'tl'},
        'PL' : {name : 'Poland', primaryLanguage : 'pl'},
        'PT' : {name : 'Portugal', primaryLanguage : 'pt'},
        'QA' : {name : 'Qatar', primaryLanguage : 'ar'},
        'RO' : {name : 'Romania', primaryLanguage : 'ro'},
        'RU' : {name : 'Russia', primaryLanguage : 'ru'},
        'SA' : {name : 'Saudi Arabia', primaryLanguage : 'ar'},
        'CS' : {name : 'Serbia', primaryLanguage : 'sr'},
        'SG' : {name : 'Singapore', primaryLanguage : 'en'},
        'SK' : {name : 'Slovakia', primaryLanguage : 'sk'},
        'SI' : {name : 'Slovenia', primaryLanguage : 'en'},
        'ZA' : {name : 'South Africa', primaryLanguage : 'en'},
        'ES' : {name : 'Spain', primaryLanguage : 'es'},
        'LK' : {name : 'Sri Lanka', primaryLanguage : 'en'},
        'SE' : {name : 'Sweden', primaryLanguage : 'sv'},
        'CH' : {name : 'Switzerland', primaryLanguage : 'de'},
        'TW' : {name : 'Taiwan', primaryLanguage : 'zh_CN'},
        'TZ' : {name : 'Tanzania', primaryLanguage : 'en'},
        'TH' : {name : 'Thailand', primaryLanguage : 'th'},
        'TT' : {name : 'Trinidad and Tobago', primaryLanguage : 'en'},
        'TN' : {name : 'Tunisia', primaryLanguage : 'ar'},
        'TR' : {name : 'Turkey', primaryLanguage : 'tr'},
        'UG' : {name : 'Uganda', primaryLanguage : 'en'},
        'UA' : {name : 'Ukraine', primaryLanguage : 'uk'},
        'AE' : {name : 'United Arab Emirates', primaryLanguage : 'en'},
        'GB' : {name : 'United Kingdom', primaryLanguage : 'en'},
        'US' : {name : 'United States', primaryLanguage : 'en'},
        'VE' : {name : 'Venezuela', primaryLanguage : 'es'},
        'VN' : {name : 'Vietnam', primaryLanguage : 'vi'},
        'YE' : {name : 'Yemen', primaryLanguage : 'ar'}
    }
    this.regionsOrder = ['G20','Europe','Americas','Africa','Oceania','Asia','Middle East','Emerging Markets','European Union'];
    this.countriesByRegion = {
        'G20' : ['AR', 'AU', 'BR', 'CA', 'CN', 'FR', 'DE', 'IN', 'ID', 'IT', 'JP', 'KR', 'MX', 'RU', 'SA', 'ZA', 'TR', 'GB', 'US'],
        'Europe' : ['AT', 'BE', 'BA', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FO', 'FI', 'FR', 'DE', 'GR', 'HU', 'IS', 'IE', 'IT', 'LV', 'LI', 'LT', 'LU', 'MC', 'NL', 'NO', 'PL', 'PT', 'RO', 'RU', 'RS', 'SK', 'Sl', 'ES', 'SE', 'CH', 'TR', 'UA', 'GB'],
        'Americas' : ['GB', 'AR', 'BB', 'BR', 'CA', 'CL', 'CO', 'CR', 'DO', 'EC', 'GL', 'IM', 'MX', 'PA', 'PE', 'TT', 'US', 'VE'],
        'Africa' : ['GB', 'DZ', 'AO', 'EG', 'ET', 'GH', 'KE', 'LY', 'MA', 'MZ', 'NG', 'ZA', 'TZ', 'TN', 'UG'],
        'Oceania' : ['GB', 'AU', 'NZ'],
        'Asia' : ['GB', 'AZ', 'BD', 'CN', 'HK', 'IN', 'ID', 'JP', 'KZ', 'KR', 'MY', 'PK', 'PH', 'SG', 'LK', 'TW', 'TH', 'VN'],
        'Middle East' : ['GB','BH','BN','IL','JO','KW','LB','PS','SA','AE'],
        'Emerging Markets' : ['GB', 'AR', 'BR', 'CL', 'CN', 'CO', 'EG', 'HK', 'IN', 'ID', 'MY', 'MX', 'MA', 'PK', 'PE', 'PH', 'PL', 'RU', 'ZA', 'TH', 'TR'],
        'European Union' : ['AT', 'BE', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'NL', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE', 'GB']
    }
    // urls
    this.appEngineBaseUrl = "http://2.latest.export-expert.appspot.com";
    this.translateBaseUrl = "http://ajax.googleapis.com/ajax/services/language/translate";
    this.exportsBaseUrl = "http://www.google.co.uk/intl/en/exportadviser";
    this.videoUrl = "http://www.youtube.com/v/vR2NqbMR8D4&hl=en&fs=1"; //"http://www.youtube.com/v/LTIdsXpq14I&hl=en&fs=1";
    this.additionalCountryInfoUrl = "http://spreadsheets.google.com/pub?key=rum2c66a2W2nCoUw_uLlLeQ";
    this.currencyConversionUrl = "http://www.google.com";

    // the rest
    this.opportunityResults = {}
    this.infoResults = {}
    this.translatedLanguages = {};
    this.googleTimeout = 5000; // Google timeouts after 5secs, so useful to keep retrying after this time has elapsed
    this.keyComparisonCountry = 'GB';
    this.forcedKeyComparisonInclusion = false;
    this.multiKeywordSeperator = '__';
    this.currentKeywords = null;
    this.cancelCurrentProcess = false;

    /*************************************************
     * Action methods
     *************************************************/
    /**
     * Populate the regionSelector based on values in available Countries
     **/
    this.populateRegionSelector = function() {
        var _self = this;
        $.each(this.regionsOrder, function(i,region){
            $('#regionSelector').append( ['<option value="',region,'" class="group">',region,'</option>'].join('') );
        });
    }

    /**
     * Begin the Guru Process
     *
     * 1. Translate Keywords
     * 2. Calculate opportunity for translated keywords
     * 3. Determine Rising/Related keywords for entered keyword
     **/
    this.startGuru = function (keywords){
        if(typeof keywords != 'undefined'){
            $('#keywords').val(keywords);
            this.currentKeywords = keywords;
        }

        if($('#regionSelector').val() == ''){
            this.cancelGuru('Please select a region');
            return;
        }

        // always reset when start
        this.cancelCurrentProcess   = false;
        this.opportunityResults       = {};
        this.infoResults            = {};

        // disable intro and results panels
        this.disableIntro();
        this.disableResults();

        // kickoff first process (which enables loader-panel)
        this.getTranslatedKeywords();
    }

    /**
     * Cancel Guru Process
     **/
    this.cancelGuru = function(message){
        this.cancelCurrentProcess = true;
        this.disableMarketDataOverlay();
        this.disableLoader();
        this.disableResults();
        this.enableIntro();

        this.currentKeywords = null;

        if(typeof message != 'undefined'){
            this.enableErrorMessage(message);
        }
        else{
            this.disableErrorMessage();
        }
    }

    /**
     * Translate Keywords into languages selected in form input
     **/
    this.getTranslatedKeywords = function () {
        this.enableLoader('Translating keyword(s)', 1);

        var keywords = this.getKeywordsArray();
        if('' == keywords[0]){
            this.cancelGuru("You must enter a keyword to translate.");
            return;
        }

        // reset
        this.translatedLanguages = {};
        for(var i=0; i<keywords.length; i++){
            var lastToComplete = (i+1 == keywords.length ? true : false);
            this.translateKeyword(keywords[i], i, lastToComplete);
        }
    }

    this.translateKeyword = function (text, keywordNum, lastToComplete){
        var _self = this;
        var enOnly = this.isEnglishOnly();

        // Must always include UK
        if(!$.isArray(this.translatedLanguages['en'])){
            this.translatedLanguages['en'] = [];
        }
        this.translatedLanguages['en'][keywordNum] = text; // untranslated
        
        if(enOnly){
            if(lastToComplete){
                //_self.getOpportunityData(); //@todo remove
                _self.getEstimateData();
            }
        }
        else{
            var fetchUrl = [this.translateBaseUrl, '?v=1.0&q=', escape(text)].join('');
            var selectedLanguages = this.getSelectedRegionsData(true).languages;
            //var selectedLanguages = google.language.Languages;
            var translatedLanguageOrder = [];
            for (var i in selectedLanguages) {
                var langCode = selectedLanguages[i];
                if (google.language.isTranslatable(langCode) && langCode != '') {
                    // That means we can translate to this language
                    // NOTE: we don't use the multiKeywordSeperator when translating,
                    // it's used for concatenating multi-keyword results only
                    var newBatchParam = ['&langpair=en%7C', langCode].join('');
                    fetchUrl += newBatchParam; // add a new language to be translated
                    translatedLanguageOrder.push(langCode);
                }
            }

            this.makeRequest(fetchUrl, function(obj){
                    // process results
                    for(var i=0; i<translatedLanguageOrder.length; i++){
                        var countryCode = translatedLanguageOrder[i];
                        var translatedKeywords = '';
                        if(obj.responseData[i] && 200 == obj.responseData[i].responseStatus){
                            // for multi-translations
                            translatedKeywords = obj.responseData[i].responseData.translatedText;
                        }
                        else if( 200 == obj.responseData.responseStatus){
                            // for single translations
                            translatedKeywords = obj.responseData.translatedText;
                        }

                        // use english keyword if no other result (well, presumably english)
                        if(!translatedKeywords){
                            translatedKeywords = _self.translatedLanguages['en'][keywordNum];
                        }

                        // clean for any dodgy html chars getting about in translation (e.g. "insurance"(fr))
                        translatedKeywords = html_entity_decode(translatedKeywords, 'ENT_QUOTES');

                        if(!$.isArray(_self.translatedLanguages[countryCode])){
                            _self.translatedLanguages[countryCode] = [];
                        }

                        _self.translatedLanguages[countryCode][keywordNum] = translatedKeywords;
                    }
                    if(lastToComplete){
                        // delay for a moment, as there are some issues with
                        // ALL translations existing in the translatedLanguages
                        setTimeout(function(){
                            //_self.getOpportunityData() //@todo remove
                            _self.getEstimateData();
                        }, 300);
                    }
                    return true;
                },
                "We cannot translate that keyword, please try another.", 3,
                function(obj){
                    // a dummy hack to raise error if the object has no responseData
                    if(!$.isArray(obj.responseData) && !obj.responseData.translatedText){
                        return false;
                    }
                    return true;
                });
        }
    }

    /**
     * Get Estimate Data for selected countries and keywords
     **/
    this.getEstimateData = function () {
        var fetchUrl = [this.appEngineBaseUrl, '/sizeOpportunity/estimate'];
        var _self = this;
        
        this.enableLoader('Calculating opportunity', 2);

        var selectedData = this.getSelectedRegionsData();
        var params = {
            'keywords'     : selectedData.translations.join(','),
            'countries'    : selectedData.countries.join(','),
            'languages'    : selectedData.languages.join(',')
        }

        $.each(params, function(key, value){
            // don't want to do a complete-escape as special/foreign chars won't be recognisable
            fetchUrl.push( ['/', key, '/', value.replace(/ /gi, '+')].join('') );
        });
        fetchUrl = fetchUrl.join('');

        this.makeRequest(fetchUrl, function(obj){
                if(obj.success){
                    _self.opportunityResults['keyword_estimate'] = obj.keyword_estimate;
                    _self.getVariationData();
                }
                else{
                    _self.cancelGuru(obj.message);
                }
                return true;
            }, "Error fetching estimates. Try again later.", 8);
    }

    /**
     * Get Variations Data for selected countries and keywords
     **/
    this.getVariationData = function () {
        var _self = this;
        var selectedData = this.getSelectedRegionsData();
        var totalDataItems = selectedData.countries.length;
        var completeResults = false;
        var maxRetries = 8;
        var failedCountries = {};

        this.enableLoader('Calculating keyword variations', 3);

        for(var i=0; i<totalDataItems; i++){
            var fetchUrl = [this.appEngineBaseUrl, '/sizeOpportunity/variation'];
            var params = {
                'keywords'   : selectedData.translations[i],
                'country'    : selectedData.countries[i],
                'language'   : selectedData.languages[i]
            }

            $.each(params, function(key, value){
                // don't want to do a complete-escape as special/foreign chars won't be recognisable
                fetchUrl.push( ['/', key, '/', value.replace(/ /gi, '+')].join('') );
            });
            fetchUrl = fetchUrl.join('');
            
            this.opportunityResults['keyword_variation'] = {};
            var dataItemCount = 0;
            this.makeRequest(fetchUrl, function(obj){
                    if(obj.success){
                        if(_args()["debug"]){
                            console.log('variation success')
                        }
                            
                        $.each(obj.keyword_variation, function(key, value){
                            _self.opportunityResults['keyword_variation'][key] = value;
                            dataItemCount++;
                        });
                    }
                    else{
                        if(obj.country){
                            var tmpCountry = obj.country; // special param returned
                            if('undefined' == typeof failedCountries[tmpCountry] || isNaN(failedCountries[tmpCountry]) || null == failedCountries[tmpCountry]){
                                failedCountries[tmpCountry] = 0;
                            }
                            failedCountries[tmpCountry]++
                            
                            if(_args()["debug"]){
                                console.log('variation fail, country:'+tmpCountry+', count:'+failedCountries[tmpCountry])
                            }

//                            if(failedCountries[tmpCountry] < maxRetries){
//                                // send back false, to retry request
//                                if(_args()["debug"])
//                                    console.log('retrying country:'+tmpCountry)
//                                return false;
//                            }
//                            else{
                            if(tmpCountry == _self.keyComparisonCountry){
                                // if key comparison country failed then abort
                                if(_args()["debug"]){
                                    console.log('variation failed for key comparison country')
                                }
                                _self.cancelGuru(obj.message);
                            }
                            else if(1 == failedCountries[tmpCountry]){
                                // delete matching keyword estimate and move on (don't destroy entire result set if one fails)
                                delete _self.opportunityResults['keyword_estimate'][tmpCountry];
                                totalDataItems--;
                                if(_args()["debug"]){
                                    console.log(tmpCountry+' variation failed, and removed', obj)
                                }
                            }
//                            }
                        }
                        else{
                            if(_args()["debug"])
                                console.log('variation failed, unknown reason!', obj)
                            // send back false, to retry request
                            return false;
                        }
                    }

                    // because I don't trust time!
                    setTimeout(function(){
                        if(dataItemCount == totalDataItems && !completeResults){
                            completeResults = true;
                            _self.calculateOpportunity();
                        }
                    },
                    300);

                    return true;
                }, "Error fetching variations. Try again later.", maxRetries);
        }
    }

    /**
     * Calculate Opportunity values for each country based on Keyword Estimate
     * and Variation data fetched.
     **/
    this.calculateOpportunity = function () {
        var _self = this;
        var keyComparisonEstimate   = this.opportunityResults['keyword_estimate'][this.keyComparisonCountry]
        var keyComparisonVariation  = this.opportunityResults['keyword_variation'][this.keyComparisonCountry]
        var countryOpportunity = {};

        $.each(_self.opportunityResults['keyword_estimate'], function(countryCode, data){

            var countryEstimate     = _self.opportunityResults['keyword_estimate'][countryCode];
            var countryVariation    = _self.opportunityResults['keyword_variation'][countryCode];
            var opportunity         = 0;

            if(0 == countryEstimate['overall_CPC']){
                opportunity = 0;
            }
            else{
                opportunity = (
                    (keyComparisonEstimate['overall_CPC'] / countryEstimate['overall_CPC'])
                    +
                    (countryVariation['sum_search_volume'] / (0==keyComparisonVariation['sum_search_volume'] ? 1 : keyComparisonVariation['sum_search_volume']))
                )
            }

            countryOpportunity[countryCode] = opportunity;
        });

        this.opportunityResults['opportunity'] = countryOpportunity;

        this.displayOpportunityData();
        this.getRelatedAndRising();
    }
    
    /**
     * Display the calculated data opportunity
     **/
    this.displayOpportunityData = function () {
        var _self = this;

        // flip data structure so that all data can be found by country 
        // (lazy - can remove this when alter all previous data structures)
        var countryArray = []
        $.each(this.opportunityResults['keyword_estimate'], function(countryCode, data){
            countryArray.push({
                'country_code'      : countryCode,
                'opportunity'       : _self.opportunityResults['opportunity'][countryCode]
            });
        });
        // order data (countries) by keyComparisonCountry, then opportunity desc
        // (the UK is always in the first place, to indicate it's a key comparitor)
        countryArray.sort(function(a,b){
            if(a.country_code == _self.keyComparisonCountry){
                return -1;
            }
            if(b.country_code == _self.keyComparisonCountry){
                return 1;
            }
            else if(a.opportunity > b.opportunity){
                return -1;
            }
            else if(a.opportunity < b.opportunity){
                return 1;
            }
            return 0;
        });

        // post ordering (note: due to UK being first, it's likely it won't be the maxOpportunity, but it could be, so testing for this!)
        var maxOpportunity = (
                            countryArray[1].opportunity < countryArray[0].opportunity
                            ? countryArray[0].opportunity // UK it max opportunity
                            : countryArray[1].opportunity // the second country is the max
                            );

        // Create opporunity_scores for each country
        _self.opportunityResults['opportunity_score'] = {};
        $.each(this.opportunityResults['keyword_estimate'], function(countryCode, data){
            _self.opportunityResults['opportunity_score'][countryCode] = (_self.opportunityResults['opportunity'][countryCode] / maxOpportunity) * 10;
        });

        this.drawMap();
        this.drawTable(countryArray);
    }

    /**
     * Get Related and Rising keywords for Keyword
     * (chosen from Keywords, and in English only)
     **/
    this.getRelatedAndRising = function (keyword) {
        var _self = this;
        var completeSizingProcess = false;
        
        if(typeof keyword == 'undefined'){
            // presume part of the guru process
            this.enableLoader('Determining Related and Rising keywords',4);
            keyword = this.getKeywordsArray()[0].replace(' ', '+');
            keyword = escape(keyword);
            completeSizingProcess = true;
        }
        else{
            keyword = keyword.replace(' ', '+');
        }

        var fetchUrl = [this.appEngineBaseUrl, '/insights/index/keyword/', keyword].join('');

        this.makeRequest(fetchUrl,
                        function(obj){
                             _self.displayRelatedAndRising(obj, keyword);
                             if(true == completeSizingProcess){
                                 /**
                                  * Because the map sometimes takes a little longer to render (even post
                                  * 'drawingDone' event has been fired), a delay is useful.
                                  **/
                                 setTimeout(function(){
                                                _self.disableLoader();
                                                _self.enableResults();
                                            },
                                            800);
                             }
                             return true;
                         }, 'Problem retrieving rising and related. Try again later.');
    }

    this.displayRelatedAndRising = function(obj, selectedKeyword){
        var _self = this;

        // set multi keywords select box
        var keywords = this.getKeywordsArray();
        $('#select-keyword').html('');
        $.each(keywords, function(i, keyword){
            $('#select-keyword').append(["<option",(keyword == selectedKeyword ? ' selected="selected"' : ''),">",keyword,"</option>"].join(''));
        });
        $('#select-keyword').change(function(){
            _self.getRelatedAndRising($('#select-keyword').val());
        });

        // hide select-keyword if only single word
        if(keywords.length == 1){
            $('#select-keyword').hide();
            $('.related-keywords h3').html(['"', keywords[0], '"'].join(''));
            $('.related-keywords h3').show();
        }
        else{
            $('#select-keyword').show();
            $('.related-keywords h3').html('');
            $('.related-keywords h3').hide();
        }

        // set related/rising keywords
        var maxKeywords = (obj.rising && 0 == obj.rising.length ? 7 : 3);
        $('#toolbar .related-keywords ul').html('');
        $('#toolbar .rising-keywords ul').html('');

        if(obj.success){
            if(obj.related && 0 < obj.related.length){
                for(var x=0; x < obj.related.length && x < maxKeywords; x++){
                    $('#toolbar .related-keywords ul').append(["<li><a href=\"#\" onclick=\"exports.startGuru('",obj.related[x],"'); return false;\">",obj.related[x],"</a></li>"].join(''));
                }
            }
            else{
                $('#toolbar .related-keywords ul').html("<li><i>None</i></li>");
            }

            if(obj.rising && 0 < obj.rising.length){
                for(var y=0; y < obj.rising.length && y < maxKeywords; y++){
                    $('#toolbar .rising-keywords').show().find('ul').append(["<li><a href=\"#\" onclick=\"exports.startGuru('",obj.rising[y],"'); return false;\">",obj.rising[y],"</a></li>"].join(''));
                }
            }
            else{
                $('#toolbar .rising-keywords').hide();
            }
        }
        else{
            $('#toolbar .related-keywords ul').html("<li><i>Problem retrieving results</i></li>");
            $('#toolbar .rising-keywords').hide();
        }
    }

    /**
     * Get tranlations, and language data associated with regions selected,
     * and return alongside countries selected.
     * Element in translations, languages, countries should relate to eac
     * other via the index.
     **/
    this.getSelectedRegionsData = function(ignoreTranslations) {
        var _self = this;
        var countries = [], languages = [], translations = [];
        var tmpCountries;
        var enOnly = this.isEnglishOnly();

        tmpCountries = this.getSelectedRegions();

        $.each(tmpCountries, function(i, countryCode){
            // ensure the countryCode is available and its primary language has
            // a translation, otherwise ignore (we must keep these array in
            // sequential order!)
            if(_self.availableCountries[countryCode] &&
               (ignoreTranslations ||
               _self.translatedLanguages[_self.availableCountries[countryCode].primaryLanguage] ||
               enOnly)
            ){
                var tmpLanguage = (enOnly ? 'en' : _self.availableCountries[countryCode].primaryLanguage);
                countries.push(countryCode);
                languages.push(tmpLanguage);
                if(true != ignoreTranslations){

                    var tmpTranslation = (enOnly ? _self.translatedLanguages['en'] : _self.translatedLanguages[_self.availableCountries[countryCode].primaryLanguage]).slice();
                    translations.push(tmpTranslation.join(_self.multiKeywordSeperator));
                }
            }
        });

        return {'countries'     : countries,
                'languages'     : languages,
                'translations'  : translations};
    }

    this.getSelectedRegions = function(){
        var region = $('#regionSelector').val();
        var countries = [];

        if('all' == region) {
            $.each(this.availableCountries, function(countryCode, attribs){
                countries.push(countryCode);
            });
        }
        else if(this.countriesByRegion[region]) {
            countries = this.countriesByRegion[region].slice(); // clone
        }
        else {
            countries = [region];
        }

        // Must always include UK
//        if(!$.inArray(this.keyComparisonCountry, countries )){
//            this.forcedKeyComparisonInclusion = true;
//            countries.push(this.keyComparisonCountry);
//        }

        return countries;
    }

    /**
     * Get Additional Country Info searches a Google Spreadsheet for additional
     * information
     **/
    this.getAdditionalCountryInfo = function(countryCode){
        var _self = this;

        if(!this.infoResults[countryCode]){
            var query = new google.visualization.Query(this.additionalCountryInfoUrl);
            // A: code, F: risk, G: copy
            query.setQuery(["SELECT F, G WHERE A = '",countryCode,"'"].join(''));
            try{
                query.send(function(response) {
                            var dataTable = response.getDataTable();
                            _self.infoResults[countryCode] = {
                                risk : dataTable.getValue(0, 0),
                                copy : dataTable.getValue(0, 1)
                            };

                            _self.displayAdditionalCountryInfo(countryCode);
                        });
            }
            catch (e){
                this.infoResults[countryCode] = {
                                risk : '',
                                copy : 'N/A'
                            };
                this.displayAdditionalCountryInfo(countryCode);
            }
        }
        else{
            this.displayAdditionalCountryInfo(countryCode);
        }
    }

    this.displayAdditionalCountryInfo = function(countryCode){

        if(this.infoResults[countryCode]){
            var additionalInfo = this.infoResults[countryCode]

            if('' != additionalInfo.risk){
                $('#market-data #risk-value').html(additionalInfo.risk);
                $('#market-data #risk-label').attr('class','mid'); //.good .mid or .bad
                $('#market-data #risk-label').html('Risk');
            }
            else{
                $('#market-data #risk-value').html('&nbsp;');
                $('#market-data #risk-label').html('&nbsp;');
            }
            
            $('#market-insight p').html(additionalInfo.copy);

            // handle enabling the market data overlay
            $('#market-data-overlay .content-loader').hide();
            $('#market-data-overlay .content').show();
        }
    }


    /**
     * Enable loader and set message
     */
    this.enableLoader = function (message, progressNum) {
        var loadingPanel = $('#loading-panel');
        var messageContainer = $('#progress div.message');

        $("#progress div.icon").removeClass('progress-1')
                                .removeClass('progress-2')
                                .removeClass('progress-3')
                                .removeClass('progress-4')
                                .addClass(['progress-',progressNum].join(''));
        messageContainer.html(message);

        if('block' != loadingPanel.css('display')){
            loadingPanel.fadeIn("normal");
        }
    }

    /**
     * Disable loader
     */
    this.disableLoader = function () {
        var loadingPanel = $('#loading-panel');
        loadingPanel.fadeOut("normal");
    }

    /**
     * Enable intro
     */
    this.enableIntro = function () {
        var introPanel = $('#intro-panel');
        introPanel.fadeIn("normal");
    }

    /**
     * Disable intro
     */
    this.disableIntro = function () {
        var introPanel = $('#intro-panel');
        introPanel.hide();
    }

    /**
     * Enable results
     */
    this.enableResults = function () {
        var resultsPanel = $('#results-panel');
        resultsPanel.fadeIn("normal");
    }

    /**
     * Disable results
     */
    this.disableResults = function () {
        var resultsPanel = $('#results-panel');
        resultsPanel.hide();
    }

    /**
     * Enable Error Message (only shown on intro panel)
     */
    this.enableErrorMessage = function (message) {
        var errorMessage = $('#error-message');
        errorMessage.html(message);
        errorMessage.show();
    }

    /**
     * Disable Error Message (only shown on intro panel)
     */
    this.disableErrorMessage = function () {
        var errorMessage = $('#error-message');
        errorMessage.hide();
    }

    /**
     * Enable Market Data Overlay (only shown on non-intro panels)
     */
    this.enableMarketDataOverlay = function (region) {
        var _self = this;
        var countryCode;
        if(typeof region == 'object'){
            countryCode = region.region;
        }
        else{
            countryCode = region;
        }

        if(this.opportunityResults['keyword_estimate'][countryCode]){
            // display overlay - set loader
            $('#market-data-overlay .content').hide();
            $('#market-data-overlay .content-loader').show();
            $('#market-data-overlay').show();

            var countryName = this.availableCountries[countryCode].name;
            var primaryLanguage = this.availableCountries[countryCode].primaryLanguage;
            var competitiveness = this.opportunityResults['keyword_variation'][countryCode].avg_competition;

            var minCPC = this.microsToCurrency(this.opportunityResults['keyword_estimate'][countryCode].min_CPC);
            var maxCPC = this.microsToCurrency(this.opportunityResults['keyword_estimate'][countryCode].max_CPC);
            var cpcValue = ['<strong>&pound;',minCPC,'</strong> to <strong>&pound;',maxCPC,'</strong>'].join('');

            var searchValue = ['<strong>',this.addCommas(this.opportunityResults['keyword_variation'][countryCode].sum_search_volume),'</strong>'].join('');

            $('#market-data-overlay h4').html(countryName);
            $('#market-data #cpc-value').html(cpcValue);
            $('#market-data #search-value').html(searchValue);

            var competitiveClass, competitiveLabel;
            if(competitiveness <= 5 && competitiveness > 3.33){
                competitiveLabel = 'high';
                competitiveClass = 'good';
            }
            else if(competitiveness <= 3.33 && competitiveness > 1.66){
                competitiveLabel = 'medium';
                competitiveClass = 'mid';
            }
            else{
                competitiveLabel = 'low';
                competitiveClass = 'low';
            }
            $('#market-data #competitive-value').html( [Math.round(competitiveness), '/5'].join('') );
            $('#market-data #competitive-value').attr('class',competitiveClass); //.good .mid or .bad

            // get seasonality chart
            // (ATM only ever care about first keyword, but would be nice to split array and pass in)
            var translatedKeywords = this.translatedLanguages[primaryLanguage][0];
            var chartUrl = ["http://www.google.com/trends/viz?",
                            "q=",translatedKeywords.replace(/ /ig, '+'),"&",
                            "geo=",countryCode,"&",
                            "date=ytd&",
                            "graph=gadget&",
                            "width=327&",
                            "height=95"].join('');
            $('#market-data #seasonality-chart').attr('src', chartUrl);

            // set determine profitability action
            $('#market-data p.actions button').click(function(){
                var url = [_self.exportsBaseUrl,'/the-bottom-line/'].join(''); //?minCPC=',minCPC,'&maxCPC=',maxCPC,'&market=',countryCode,'&keyword=',escape(_self.getKeywords());
                parent.location = url;
            })

            // get and set additional country data (also handles showing hidden market data)
            this.getAdditionalCountryInfo(countryCode);
        }
    }

    /**
     * Disable Map Overlay (only shown on non-intro panels)
     */
    this.disableMarketDataOverlay = function () {
        var dataOverlay = $('#market-data-overlay');
        dataOverlay.hide();
    }

    /**
     * GeoMap render
     */
    this.drawMap = function () {
        var _self = this;

        var keyComparisonCountryMinCPC = this.microsToCurrency(this.opportunityResults['keyword_estimate'][this.keyComparisonCountry].min_CPC);
        var keyComparisonCountryMaxCPC = this.microsToCurrency(this.opportunityResults['keyword_estimate'][this.keyComparisonCountry].max_CPC);

        var data = new google.visualization.DataTable();
        data.addColumn('string', 'Country');
        data.addColumn('number', ['(UK: £',keyComparisonCountryMinCPC,' - £',keyComparisonCountryMaxCPC,') Click for more    '].join(''));
        data.addColumn('string', 'Title');

        // count keys in object
        var rowCount = 0;
        $.each(this.opportunityResults['keyword_estimate'], function (key, value){
            rowCount++;
        })
        data.addRows(rowCount);
        var i = 0;
        $.each(this.opportunityResults['keyword_estimate'], function(countryCode,ignoredData){
            var opportunityScore = _self.getSimpleScore(_self.opportunityResults['opportunity_score'][countryCode]).value;
            var minCPC = _self.microsToCurrency(_self.opportunityResults['keyword_estimate'][countryCode].min_CPC);
            var maxCPC = _self.microsToCurrency(_self.opportunityResults['keyword_estimate'][countryCode].max_CPC);
            var title = [countryCode,': CPC £',minCPC,' - £',maxCPC].join('');

            data.setValue(i, 0, countryCode);
            data.setValue(i, 1, opportunityScore);
            data.setValue(i, 2, title);
            i++;
        });

        var options = {
            dataMode: "regions",
            width: "576px",
            height: "315px",
            showLegend: false,
            colors: [0xf0f7fc, 0x0066cc]
        };
        var container = $("#map .data").get(0);
        var geomap = new google.visualization.GeoMap(container);
        google.visualization.events.addListener(geomap,
                                                'regionClick',
                                                function(obj){
                                                    _self.enableMarketDataOverlay(obj);
                                                });
        geomap.draw(data, options);
    }

    /**
     * drawTable updates the table display with CPC and Search data per country
     *
     * requires countryOrder to be predetermined (done elsewhere as several params
     * are gathered from the calculation)
     **/
    this.drawTable = function (countryOrder) {

        var topCountryLimit = 9;
        var container = $("#table .data table tbody");
        container.html(''); // clear

        for(var i=0; i<countryOrder.length && i<topCountryLimit; i++){
            var countryCode = countryOrder[i].country_code;
            var opportunityScore = this.getSimpleScore(this.opportunityResults['opportunity_score'][countryCode]).value;
            var minCPC = this.microsToCurrency(this.opportunityResults['keyword_estimate'][countryCode].min_CPC);
            var maxCPC = this.microsToCurrency(this.opportunityResults['keyword_estimate'][countryCode].max_CPC);
            var searchVol = this.addCommas(this.opportunityResults['keyword_variation'][countryCode].sum_search_volume);
            var title = this.availableCountries[countryCode].name;
            
            var row = ["<tr>",
                        "<td>",opportunityScore,"</td>",
                        "<td><a href=\"#\" onclick=\"exports.enableMarketDataOverlay('",countryCode,"'); return false;\">",title,"</a></td>",
                        "<td class=\"range\"><em>",searchVol,"</em></td>",
                        "<td class=\"range\"><em>&pound;",minCPC,"</em> to <em>&pound;",maxCPC,"</em></td>",
                       "</tr>"].join('');

            container.append(row);
        }
    }

    /*************************************************
     * Helper methods
     *************************************************/
    this.isEnglishOnly = function (){
        return $('#enOnly').attr("checked");
    }

    this.getKeywords = function (){
        if(!this.currentKeywords){
            this.currentKeywords = $('#keywords').val();
        }
        return this.currentKeywords;
    }

    this.getKeywordsArray = function (){
         var keywordsArray = [];
         var tmpKeywordsArray = this.getKeywords().split(',');
         $.each(tmpKeywordsArray, function (i, keyword){
             keywordsArray.push($.trim(keyword));
         });
         return keywordsArray;
    }

    this.microsToCurrency = function (microsVal){
        var microsFractionOfFundamentalCurrency = 1000000;
        return (microsVal / microsFractionOfFundamentalCurrency).toFixed(2);
    }

    this.clickVol = function (value){
        return (parseFloat(value) * 30).toFixed(0);
    }

    this.getSimpleScore = function(score){
        var labelStr, valueStr;
        if(score >= 6.66){
            labelStr = 'high';
        }
        else if(score >= 3.33){
            labelStr = 'medium';
        }
        else{
            labelStr = 'low';
        }

        valueStr = Math.round(score);

        return {
            label : labelStr,
            value : valueStr
        }
    }

    /*************************************************
     * Util methods
     *************************************************/

    /**
     * makeRequest is an abstract of _IG_FetchContent
     * Due to a severe lack of trust for _GI_Fetch* this
     * uses looping function as a workaround for _IG_Fetch* timeout problems
     *
     * If failureMessage is boolean false, then we assume operation is
     * non-critical (or not applicable to the cancelCurrentProcess method),
     * and we don't execute cancelCurrentProcess.
     */
    this.makeRequest = function (fetchUrl, callback, failureMessage, fetchLoopLimit, testResponseCallback){
        var _self = this;
        if('undefined' == typeof fetchLoopLimit || isNaN(fetchLoopLimit) || null == fetchLoopLimit){
            fetchLoopLimit = 3;
        }
        var fetchCount = 0;
        var errorRaised = false;
        var fetchCompleted = false;

        var fetchFunc = function (fetchUrlLocal) {
            fetchCount++;
            _IG_FetchContent(fetchUrlLocal, function(data){
                if('' != data && !fetchCompleted && !_self.cancelCurrentProcess) {
                    try {
                        var obj = eval(['(',data,')'].join(''));
                        if(typeof testResponseCallback == 'function'){
                            if(!testResponseCallback(obj) && !errorRaised){
                                errorRaised = true;
                                if(_args()["debug"])
                                    console.log('Request failed: test failed', fetchUrl, obj);
                                if(failureMessage){
                                    _self.cancelGuru(failureMessage);
                                }
                                return;
                            }
                        }
                    }
                    catch (e){
                        if(!errorRaised){
                            errorRaised = true;
                            if(_args()["debug"])
                                console.log('Request failed: unexpected response', fetchUrl, obj, e);
                            if(failureMessage){
                                _self.cancelGuru(failureMessage);
                            }
                        }
                        return;
                    }

                    fetchCompleted = callback(obj);
                }
                // else - unfortunately HTTP errors aren't passed to the callback!
            });

            if(fetchCount < fetchLoopLimit) {
                setTimeout(function(){
                    if(!fetchCompleted && !_self.cancelCurrentProcess){
                        if(_args()["debug"])
                            console.log('Request failed: timeout, now retrying', fetchUrl);
                        var sep = (fetchUrl.indexOf("?") > -1 ? "&" : "?");
                        var timestamp = new Date().getTime();
                        fetchFunc([fetchUrl, sep, 'nocache=', timestamp].join(''));
                    }
                }, _self.googleTimeout);
            }
            else{
                if(_args()["debug"])
                    console.log('Request failed: Max retries reached', fetchUrl);
                if(failureMessage){
                    _self.cancelGuru(failureMessage);
                }
                return;
            }
        }
        
        // kick off
        fetchFunc(fetchUrl);
    }

    this.addCommas = function(nStr){
        nStr += '';
        x = nStr.split('.');
        x1 = x[0];
        x2 = x.length > 1 ? ['.', x[1]].join('') : '';
        var rgx = /(\d+)(\d{3})/;
        while (rgx.test(x1)) {
            x1 = x1.replace(rgx, ['$1', ',', '$2'].join(''));
        }
        return x1 + x2;
    }

    /**
     * Init object
     * - fire Gadget.onLoaded when loaded
     */
    this.onLoaded=function(){
        var _self = this;

        // IE conditionals aren't being recognised in browser - so to ensure it's working do this!
        if($.browser.msie){
             if($.browser.version.substr(0,1)=="6"){
                 $('head').append('<link rel="stylesheet" href="http://opportunity-sizing.appspot.com/resources/css/ie6.css" type="text/css" />');
             }
             if($.browser.version.substr(0,1)=="7"){
                 $('head').append('<link rel="stylesheet" href="http://opportunity-sizing.appspot.com/resources/css/ie7.css" type="text/css" />');
             }
        }

        // neaten up any ajax links
        $('a').click(function() {
            this.blur();
        });

        // Keyword validation on the Intro form
        $("#keywords").keyup(function (e) {
            if($("#keywords").val() != ''){
                _self.disableErrorMessage();
            }
            /*else {
                _self.enableErrorMessage('Please enter a keyword');
            }*/
        });

        // ensure enter on Keyword form doesn't submit to form, but starts Guru
        $("#form-outer form").submit(function(){
            return false;
        });
        $("#form-outer form").keydown(function (e) {
            if(e.keyCode == 13) {
                _self.startGuru();
                $("#form-outer form").blur();
            }
        });

        // show help overlay on start screen
        $('#form-outer .help-button').click(function(){
            $('#help-message').toggle();
        });

        // Add event to map overlay close link
        $('#market-data-overlay .close-button').click(function(){
           _self.disableMarketDataOverlay();
           return false;
        });

        // Add event to results toolbar "Start again" link
        $('#toolbar .reset-link a').click(function(){
             _self.cancelGuru();
             return false;
        });

        // Add event to select display type on tab click
        $('.display-select a').click(function(){
            _self.disableMarketDataOverlay();
            $('#map, #table').hide().filter(this.hash).show();
            $('.display-select li').removeClass('active');
            $(this).parent().addClass('active');
            return false;
        });

        // Add event on the Cancel Link
        $('#cancel-link').click(function(){
            _self.cancelGuru();
            return false;
        });

        // determine is homepage view (with video) or not
        if(_args()["homepageview"]){
            $('#frame #border').attr('class', '');
            var container = $("#video").get(0);
            _IG_EmbedFlash(this.videoUrl, container, {width : 282, height : 234})
        }
        else{
            $('#frame #border').attr('class', 'small');
        }

        // populate select region
        this.populateRegionSelector();

        // initiate tabs on map overlay
        $("#market-data-overlay .content").tabs();

        // add default keyword
        var defaultKeywords = 'laptops, cameras';
        var setDefaultKeywords = function(){
            if($('#keywords').val() == ''){
                $('#keywords').val(defaultKeywords).addClass('default');
            }
        }
        setDefaultKeywords();
        $('#keywords').blur(setDefaultKeywords);
        $('#keywords').focus(function(){
            if($('#keywords').val() == defaultKeywords && $('#keywords').hasClass('default')){
                $('#keywords').val('').removeClass('default');
            }
        });

        $('#disclaimer-link, .moreinfo a').click(function(){
            $('#disclaimer').fadeIn();
            return false;
        });
        $('#disclaimer .close-button').click(function(){
            $('#disclaimer').fadeOut();
            return false;
        });

        $('#badge').addClass('past');

    };

    this.init = function(){
        var _self=this;
        _IG_RegisterOnloadHandler(function(){_self.onLoaded()});
    };

    this.init();
}

exports = new Exports();

