/*! Chinese region picker - v0.0.1 - 2013-10-24
* https://github.com/xixilive/chineserp
* Copyright (c) 2013 xixilive; Licensed MIT */
(function($) {
  'use strict';

  //ECMA262-5 methods: Array#indexOf
  if (!('indexOf' in Array.prototype)) {
    Array.prototype.indexOf= function(find, i) {
      if (i === undefined){ i= 0; }
      if (i<0){ i+= this.length; }
      if (i<0){ i= 0; }
      for (var n= this.length; i<n; i++){
        if (i in this && this[i]===find){
          return i;
        }
      }
      return -1;
    };
  }

  //ECMA262-5 methods: Array#forEach
  if(!('forEach' in Array.prototype)) {
    Array.prototype.forEach = function(action, that) {
      for (var i= 0, n = this.length; i<n; i++){
        if (i in this){
          action.call(that, this[i], i, this);
        }
      }
    };
  }

  //ECMA262-5 methods: Array#map
  if(!('map' in Array.prototype)) {
    Array.prototype.map = function(mapper, that) {
      var other= new Array(this.length);
      for (var i = 0, n = this.length; i<n; i++){
        if (i in this){
          other[i] = mapper.call(that, this[i], i, this);
        }
      }
      return other;
    };
  }

  //ECMA262-5 methods: Array#filter
  if(!('filter' in Array.prototype)){
    Array.prototype.filter = function(filter, that) {
      var other= [], v;
      for (var i=0, n= this.length; i<n; i++){
        if (i in this && filter.call(that, v= this[i], i, this)){
          other.push(v);
        }
      }
      return other;
    };
  }

  var root = this;
  var ChineseRegion = root.ChineseRegion = {};

  ChineseRegion.$ = $;
  ChineseRegion._caches = {};

  // Prototype getJSON wrapper
  if(ChineseRegion.$ && !ChineseRegion.$.getJSON && root.Ajax){
    ChineseRegion.$.getJSON = function(){
      var url = arguments[0], data, callback;
      switch(arguments.length){
        case 2:
          data = null;
          callback = arguments[1];
          break;
        case 3:
          data = arguments[1];
          callback = arguments[2];
          break;
      }
      if(typeof callback !== 'function'){ callback = function(){}; }
      var request = new root.Ajax.Request(url, {method: 'get', parameters: data, evalJSON: 'force', onSuccess: function(t){
        callback(t.responseJSON);
      }});
      return request.transport;
    };
  }

  ChineseRegion.getJSON = function(){
    return ChineseRegion.$.getJSON.apply(ChineseRegion.$, arguments);
  };

  // return true given argument is a function, otherwise false
  ChineseRegion.ifn = function(f){
    return typeof f === 'function';
  };

  /**
    A collection that contains all cities and suburbs of a province
    @param {Array} data, the data to be stored
    @param {String} name, use as collection name
    @return RegionCollection object
    @example: new RegionCollection([...], 'collection1')
  */
  var RegionCollection = ChineseRegion.RegionCollection = function(data, name){
    this.collection = data || [];
    this.name = name;
    return this;
  };

  RegionCollection.prototype = {
    constructor: RegionCollection,

    select: function(f){
      return this.collection.filter(f);
    },

    map: function(f){
      return this.collection.map(f);
    },

    first: function(f){
      return this.select(f)[0];
    },

    /**
      Find regions by region names in current RegionCollection object
      by default, this function will select the first element in array when expect region is missing.
      @param {String} a, the first region name
      @param {String} b, the second region name
      @param {String} options, your can specify which key to find in object
      @return {Array}
      @exapmle: findByNames('上海市','杨浦区') should return [{n:上海市,...}, {n:杨浦区,...}],
                findByNames('江苏省','南京市','玄武区') should return [{n:江苏省,...}, {n:南京市,...}, {n:玄武区,...}],
                findByNames('上海','崇明', {key: 'a'}) should return [{n:上海市,...}, {n:崇明县,...}],
                findByNames('上海市','卢湾区') should return [{n:上海市,...}, {n:黄浦区,...}], because '卢湾区' is not exists
    */
    findByNames: function(a, b, options){
      options = options || {};
      options.key = options.key || 'n'; //default to find by region name

      var arr = [];
      if(a){
        arr[0] = this.first(function(d){
          return d[options.key] === a;
        }) || this.collection[0];
      }
      
      if(b && arr[0] && arr[0].c){
        arr[1] = arr[0].c.filter(function(d){ return d[options.key] === b; })[0] || arr[0].c[0];
      }
      return arr;
    },

    /**
      Find regions by region ID in current RegionCollection object
      by default, this function will select the first element in array when expect region is missing.
      @param {String} id
      @return {Array}
      @exapmle: findById('310105') should return [{i:310000,...}, {i:310105,...}] of Shanghai city,
                findById('310103') should return [{i:310000,...}, {i:310101,...}], because '310103' is not exists
    */
    findById: function(id){
      var arr = [];
      arr[0] = this.first(function(d){
        return d.i === id;
      }) || this.first(function(d){
        return d.i === id.substr(0,4) + '00';
      }) || this.collection[0];

      if(arr[0] && arr[0].c){
        arr[1] = arr[0].c.filter(function(d){ return d.i === id; })[0] || arr[0].c[0];
      }
      return arr;
    }
  };

  /*
    DataProxy, load data from remote json files, and cache these files
    the callback function will be call with 2 arguments, the first one is DataProxy object,
    and another one is RegionCollection object
    @param {String} remote, the URI path of remote files
    @param {Function} init, callback function
    @return {Object} DataProxy object
    @example: new DataProxy('/remote/', function(proxy, collection){})
  */
  var DataProxy = ChineseRegion.DataProxy = function(remote, init){
    this._remote = (remote  + '/').replace(/\/+/g,'/');

    this.load('index', function(collection, proxy){
      if(ChineseRegion.ifn(init)){ init(proxy, collection); }
    });
    return this;
  };

  DataProxy.prototype = {
    /**
      Convert region ID to special format, in order to match the json filename
      @param {String} id
      @return {String}
      @example: _index('310105') should get '310000'
    */
    _index: function(id){
      return id.replace(/\d{4}$/,'0000');
    },

    /**
      Convert region ID to special cache-id format, in order to cache a collection totally
      @param {String} id
      @return {String}
      @example: _cacheid('310105') should get 'cached_310000'
    */
    _cacheid: function(id){
      return 'cached_' + this._index(id);
    },

    /**
      Convert region ID to related json-file's url
      @param {String} id
      @return {String}
      @example: _url('310105') should get '/a_remote_path/310105.json'
    */
    _url: function(id){
      return this._remote + this._index(id) + '.json';
    },

    /**
      Load data asynchoronize, the callback function will call with 2 arguments, 
      the first is RegionCollection object, 
      and the second is DataProxy object

      @param {String} id
      @param {Function} callback function, 
      @exapmle load('310105', function(collection, proxy){})
    */
    load: function(id, f){
      var self = this, cache_id = this._cacheid(id);
      if(!ChineseRegion.ifn(f)){ f = function(){}; }

      if(ChineseRegion._caches[cache_id]){
        f(new RegionCollection(ChineseRegion._caches[cache_id], cache_id),self);
        return;
      }

      ChineseRegion.getJSON(this._url(id), function(data){
        ChineseRegion._caches[cache_id] = data;
        f(new RegionCollection(data, cache_id), self);
      });
    },

    /**
      Provide cached collections
      @return {Array}
    */
    collections: function(){
      var coll = [];
      for(var i in ChineseRegion._caches){
        coll.push(new RegionCollection(ChineseRegion._caches[i], i));
      }
      return coll;
    },

    /**
      Get a collection named the specified argument
      @param {String} value
      @return {Array}
      @example: collection('index'), collection('310105')
    */
    collection: function(value){
      var cid = this._cacheid(value);
      return this.collections().filter(function(c){ return c.name === cid; })[0];
    },

    /**
      Provide the index collection
      @return {Array}
    */
    indices: function(){
      return this.collection('index');
    }
  };

  /*
    RegionPicker, the initialized callback will be call with RegionPicker object
    @param {Object} options
    @example:  new RegionPicker({remote: '/', initialized: function(picker){
      picker.pick('310102', function(regions){ console.log( regions.map(function(d){ return d.n; }).join(" > "); ); })
      picker.pick('PROVINCE,CITY,SUBURB', function(regions){ console.log( regions.map(function(d){ return d.n; }).join(" > "); ); })
    }})
    @return RegionPicker object
  */
  var RegionPicker = ChineseRegion.RegionPicker = function(options){
    var self = this;
    this.options = options;
    new DataProxy(options.remote || '',  function(proxy){
      self.initialize(proxy);
    });
    return self;
  };

  RegionPicker.prototype = {
    initialize: function(proxy){
      this.proxy = proxy;
      if(ChineseRegion.ifn(this.options.initialized)){ this.options.initialized(this); }
    },

    /**
      pick up a specified region
      @param {String | Array} value
      @param {Object} options
      @param {Function} callback
      @return undefined
      @example pick(value, callback)
      @example pick(value, options, callback)
    */
    pick: function(){
      var value = arguments[0], options = {}, f;

      switch(arguments.length){
        case 2:
          f = arguments[1];
          break;
        case 3:
          options = arguments[1];
          f = arguments[2];
          break;
      }

      if(!ChineseRegion.ifn(f)){ f = function(){}; }

      var proxy = this.proxy;
      if(!this.proxy){ f([]); return; }

      if(value === null || value === ''){
        value = proxy.indices().collection[0].i;
      }

      if(/^\d+$/.test(value)){ //pickById
        this._pickById(value, f);
        return;
      }else{ //pickByNames
        if(typeof value === 'string'){ value = value.split(/[,\s]+/,3); }
        this._pickByNames(value, options, f);
        return;
      }
      f([]);
    },

    /**
      Provide array that is the super-collection of regions
      @param {Array} regions
      @return {Array} a array contains 3 or less elements, in same order as the elements in regions argument
    */
    _pickedCollections: function(regions){
      if(!regions || !regions.map){
        return [];
      }
      var collections = [];
      if(regions[0]){
        collections.push(this.proxy.collection('index').collection);
      }
      if(regions[1]){
       collections.push(this.proxy.collection(regions[1].i).collection); 
      }
      if(regions[2] && regions[1].c){
       collections.push(regions[1].c); 
      }
      return collections;
    },

    /**
      Pickup regions via specified ID
      @param {String} id
      @param {Function} f, a callback function that will be call when regions have picked
    */
    _pickById: function(id, f){
      var self = this, proxy = this.proxy, regions = [];
      regions[0] = proxy.indices().first(function(r){
        return r.i === id.substr(0,2) + '0000';
      });
      proxy.load(id, function(c){
        regions = regions.concat(c.findById(id));
        f(regions, self._pickedCollections(regions));
      });
    },

    /**
      Pickup a region via specified string seperated with comma or space
      @param {String} names
      @param {Object} options
      @param {Function} f, a callback function that will be call when regions have picked
    */
    _pickByNames: function(names, options, f){
      var self = this, proxy = this.proxy, regions = [];
      regions[0] = proxy.indices().first(function(r){
        return r.n === names[0];
      });
      if(!regions[0]){
        f([]);
        return;
      }
      proxy.load(regions[0].i, function(c){
        regions = regions.concat(c.findByNames(names[1],names[2],options));
        f(regions, self._pickedCollections(regions));
      });
    }
  };

}).call(this, this.jQuery || this.$ || this.Zepto || this.Prototype);