var OpalRequest = require('./OpalRequest').OpalRequest;
var OpalLoader = require('./OpalLoader').OpalLoader;
var Loader = require('../net/Loader').Loader;
var Types = require('../common/Types');

var OpalError = function (message) {
    this.name = "OpalClientError";
    this.message = message;
};

OpalError.prototype = new Error();
OpalError.prototype.constructor = OpalError;

/**
  * @class: OpalFactory
  * @description: Factory with default dependencies. Used for easy dependency injection.
  * @author: ssyta
  */
var OpalFactory = function (gateway){
    this.OpalRequest = OpalRequest;
    this.OpalLoader = OpalLoader;
    this.Loader = Loader;
    this.OpalError = OpalError;
    this.createOpalRequest = function (request_data){
        var request = new this.OpalRequest(request_data);
        var opalLocation = process.env.OPAL_LOCATION;

        if (gateway) {
            request.setConnectionHost(gateway);
        } else if (!!opalLocation) {
            request.setConnectionHost(opalLocation);
        }

        return request;
    };
    this.createOpalLoader = function (request) {
        return new this.OpalLoader(request);
    };
    this.createLoader = function () {
        return new Loader();
    };
    this.createError = function (message) {
        return new OpalError(message);
    };
};

/**
  * @class: OpalClientPrototype
  * @description: Abstract OpalClient. It doesn't provide auto-registration of methods.
  * @author: ssyta
  */
var OpalClientPrototype = function(service, _timeout, _OpalFactory){
    this._OpalFactory = _OpalFactory || new OpalFactory();
    this.service = service;
    this.timeout = (_timeout || 1) * 1000;
};


/**
  * @function: _register_methods
  * @description: Register methods for opalrpc invocation.
  *
  * @author: ssyta
  */
OpalClientPrototype.prototype._register_methods = function (method_names) {
    var that = this;
    method_names.forEach(function (name) {
        that[name] = function (params, callback) {

            if (callback === undefined) {
                throw that._OpalFactory.createError("No callback specified")
            }

            var request_data = {
                url: that.service,
                method: name,
                params: params || null
            };

            console.log('Calling', name, 'on', that.service);

            var request = that._OpalFactory.createOpalRequest(request_data);
            var loader = that._OpalFactory.createOpalLoader(request);

            loader.setTimeout(that.timeout);
            loader.addEventListener(that._OpalFactory.OpalLoader.Event.JSON_RESPONSE, on_response);
            loader.addEventListener(that._OpalFactory.Loader.Event.ERROR, on_error);

            try {
                loader.load();
            } catch (err) {
                return callback(err);
            }

            function on_response(response) {
                callback(response.data.getBody().getError(), response.data.getBody().getResult());
            }

            function on_error(err) {
                callback(err);
            }
        };
    });
};

/**
  * @class: OpalClient
  * @description: Easy to use opalrpc client. Callback function is necessary!
  * @example:  var ws = new OpalClient('http://workspace.pulscms.onetapi.pl', ['get', 'read_attr']);
  *            ws.get([...args...], callback);
  *             or
  *            ws.get({arg1: 1, arg2: 2, ... }, callback);
  * @author: ssyta
  */
var OpalClient = function(service, methods, _timeout, _OpalFactory){
    if (Types.isObject(service)) {
        OpalClientPrototype.apply(this, [service.INTERFACE, service.TIMEOUT, _OpalFactory]);
        this._register_methods(service.METHODS);
    } else {
        OpalClientPrototype.apply(this, [service, _timeout, _OpalFactory]);
        this._register_methods(methods);
    }
};

OpalClient.prototype = Object.create(OpalClientPrototype.prototype);

exports.OpalClient = OpalClient;
exports.OpalError = OpalError;
