BarGraph = function(options){
    this.options = $.extend({
        holder_id : 'graph',
        height : 200,
        width : 600,
        bar_events: {}
    }, options || {});

    this.raphael = Raphael(this.options.holder_id, this.options.width, this.options.height);
}

BarGraph.prototype.getLeftOffset = function(graph_max) {
    var t_obj = this.raphael
            .text(0, 0, graph_max)
            .attr({fill: "#000", font: '10px Fontin-Sans, Arial'})
    var rc= t_obj.getBBox().width;
    t_obj.remove();
    return rc;
}

BarGraph.prototype.drawLeftLabels = function(bottom_offset, lines) {
    // Подписи по левую сторону графика
    var h_pos, iter, labels = this.raphael.set();
    for (iter = lines - 1; iter > 0; iter--) {
        // Положение точки по горизонтали
        h_pos = Math.round( (this.options.height - bottom_offset) / lines * iter );
        // Подпись слева от графика
        labels.push(this.raphael.text(0, h_pos, Math.pow(10, lines - iter)));
    }
    var labels_pos = labels.getBBox();
    if (labels_pos.x < 0) {
        labels.attr({'x': -labels_pos.x});
    }
    return labels;
}

BarGraph.prototype.drawBottomLabels = function(left_offset, bar_width, data) {
    var attributes = {fill: "#000", font: '10px Fontin-Sans, Arial'},
        rc = this.raphael.set(), _self = this;
    //
    $.each(data, function(idx, el){
        rc.push(
            _self.raphael
                .text(left_offset + bar_width / 2, _self.options.height, el[1])
                .attr(attributes).rotate(90)
        );
        left_offset += bar_width + 1;
    });
    return rc;
}

BarGraph.prototype.drawGrid = function(left_offset, bottom_offset, lines) {
    var height = this.options.height - bottom_offset,
        width =  this.options.width - left_offset;

    // 2 базовые линии (оси)
    this.raphael
        .path([
            "M", left_offset, 0,
            "v", height,
            'h', width
        ])
        .attr({stroke: '#000'})
        .toBack()
    ;
    // Шкала
    var h_pos, iter, path = [];
    for (iter = 1; iter < lines; iter++ ) {
        // Положение точки по горизонтали
        h_pos = Math.round( height / lines * iter );
        path = path.concat(["M", left_offset, height - h_pos, 'h', width]);
    }
    return this.raphael.path(path).attr({stroke: '#ada688'}).toBack();
}

BarGraph.prototype.fillByType = function(type) {
    var rc;
    switch (type) {
        case 1:rc = "90-#05721d-#00a524";
            break;
        case 2:rc = "90-#56662d-#84b70e";
            break;
        case 3:rc = "90-#77602b-#f9ab02";
            break;
        case 4:rc = "90-#724c32-#e06c1d";
            break;
        default:rc = "90-#542d2d-#820b0b";
            break;
    }
    return rc;
}

BarGraph.prototype.drawBar = function(x_top_left, y_top_left, width, height, type) {
    return this.raphael.rect(x_top_left, y_top_left, width, height).attr({
        'stroke' : 'none',
        fill: this.fillByType(type)
    });
}

BarGraph.prototype.getBarFillType = function (min, curr) {
    //return Math.round(curr / min);
    var delta = curr - min;
    if (delta < 0.001) {
        return 1;
    } else if ( delta < 0.01 ) {
        return 2;
    } else if ( delta < 0.1 ) {
        return 3;
    } else if (delta < 1) {
        return 4;
    } else {
        return 5;
    }
}

BarGraph.prototype.lg = function(x) {
    return Math.log(x) / Math.LN10;
}

BarGraph.prototype.draw = function(data) {
    var min = NaN, max = 0, MIN_HEIGHT = 3,
        total_amount = 0, _self = this;
    // Минимальные / максимальные значения
    $.each(data.values, function(idx, el){
        if (max < el[0]) max = el[0];
        if (min > el[0] || isNaN(min)) min = el[0];
        total_amount += el[0];
    });

    var bottom_offset = 0,
        total_lines = Math.ceil(this.lg(max)),
        graph_max = Math.pow(10, total_lines),
        left_offset = this.getLeftOffset(graph_max)
    ;
    this.clear();
    // Ширина 1го бара
    var bar_width = Math.floor((this.options.width - left_offset - data.values.length + 2 ) / data.values.length),

    // Подписи внизу
    bottom_labels = this.drawBottomLabels(left_offset, bar_width, data.values);
    $.each(bottom_labels.items, function(idx, l){
        bottom_offset = Math.max(l.getBBox().width, bottom_offset);
    })
    bottom_labels.attr('y', Math.round(this.options.height - (bottom_offset += 2) /2) );

    // Подиси слева
    this.drawLeftLabels(bottom_offset, total_lines),

    // Сетка
    this.drawGrid(left_offset, bottom_offset, total_lines);
    var k = Math.abs((this.options.height - bottom_offset) / this.lg(min / graph_max));

    //  Рисуем бары
    var left_amount = 0, right_amount = total_amount;
    $.each(data.values, function(idx, el){
        var val = el[0], i, 
            type = _self.getBarFillType(data.base, el[1]),
            bar_height = k * Math.abs(_self.lg(min / val)) + MIN_HEIGHT;
        // Бар на графике
        var bar = _self.drawBar(left_offset + 1, _self.options.height - bottom_offset - bar_height, bar_width, bar_height, type);
        for (i in _self.options.bar_events) {
            if (_self.options.bar_events.hasOwnProperty(i)) bar[i](_self.options.bar_events[i]);
        }
        right_amount -= el[0];
        bar.info = {
            'price' : el[1],
            'amount' : el[0],
            'left_amount' : left_amount,
            'right_amount' : right_amount
        }
        left_amount += el[0];
        left_offset += bar_width  + 1;
    });
}

BarGraph.prototype.clear = function() {
    this.raphael.clear();
}

BarGraph.prototype.destroy = function() {
    this.clear();
    this.raphael.remove();
}

CandleStickGraph = function(options) {
    this.options = $.extend({
        holder_id: 'graph',
        width: 700,
        height: 200,
        candle_width: 7,
        grid_color : '#d5d3c4',
        price_up_color: '#578934',
        price_down_color: '#bf3e42'
    }, options || {});
    //
    this.width
    //
    this.scale_k = NaN;
    this.graph_min_price = NaN;
    this.graph_max_price = NaN;
    //
    this.raphael = Raphael(this.options.holder_id, this.options.width, this.options.height);
    this.raphael.safari();
}

CandleStickGraph.prototype.getRenderedTextLen = function(text) {
    var t_obj = this.raphael
            .text(0, 0, text)
            .attr({fill: "#000", font: '10px Fontin-Sans, Arial'})
    var rc= t_obj.getBBox().width;
    t_obj.remove();
    return rc;
}

CandleStickGraph.prototype.initValues = function(data) {
    var graph_min_price = NaN,
        graph_max_price = NaN,
        l_bbox = this.getLabelBBox()
    // Определяем минимальные значения цен в наборе данных
    $.each(data, function(idx, el){
        var group_min = NaN,
            group_max = NaN;
        //
        for (var i = 1; i < el.length; i++) {
            if (isNaN(group_min) || group_min > el[i])
                group_min = el[i];
            if (isNaN(group_max) || group_max < el[i])
                group_max = el[i];
        }

        if (isNaN(graph_min_price) || graph_min_price > group_min)
            graph_min_price = group_min;
        if (isNaN(graph_max_price) || graph_max_price < group_max)
            graph_max_price = group_max;
    });
    this.graph_min_price = graph_min_price - 0.001;
    this.graph_max_price = graph_max_price + 0.001;
    //
    this.right_offset = l_bbox.width + 1
    this.bottom_offset = l_bbox.height
    //
    this.scale_k =
        (this.options.height - this.bottom_offset) / (this.graph_max_price - this.graph_min_price);
}

CandleStickGraph.prototype.getYOffsetByVal = function(val, down) {
    var rc = this.options.height - this.bottom_offset - Math[down ? 'ceil' : 'floor']((val - this.graph_min_price) * this.scale_k);
    return rc > 0 ? rc : 0;
}

CandleStickGraph.prototype.getLabelBBox = function() {
    var t_obj = this.raphael.text(0, 0, '0.0000').attr({fill: "#000"}),
        rc = t_obj.getBBox();
    t_obj.remove();
    return rc;
}

CandleStickGraph.prototype.drawRightLabel = function(label, y_pos) {
    this.raphael.text(this.options.width - this.right_offset / 2 + 2, y_pos, label);
}

CandleStickGraph.prototype.drawGrid = function() {
    this.right_offset +=2;
    this.raphael.rect(0.5, 0.5, this.options.width - this.right_offset + .5, this.options.height - this.bottom_offset - 1);

    var path = [], y_offset,
        step = (this.graph_max_price - this.graph_min_price) / 10;
    if (step < .0001) step *= 2
    for (var i = this.graph_min_price; i <= this.graph_max_price; i += step) {
        y_offset = this.getYOffsetByVal(i);
        path = path.concat(['M', 0, y_offset + .5, 'h', this.options.width - this.right_offset]);
        this.drawRightLabel(i.toFixed(4), y_offset, this.right_offset - 2)
    }

    this.raphael.path(path).attr({
        'stroke-width': 0.8,
        'stroke': '#d5d3c4',
        'stroke-dasharray': '--',
        'stroke-linecap' : 'square'
    }).toBack();
}

CandleStickGraph.prototype.drawVerticalGrid = function(left_offset, width, freq) {
    var s = 1, i, path = [], freq = freq || 1

    for (i = 60; i > 0; i--) {
        if (s++==freq) {
            s = 1;
            path = path.concat(['M', left_offset + width/2, 0, 'v', this.options.height - this.bottom_offset]);
        }
        left_offset += width + 3.75;
    }

    return this.raphael.path(path).attr({
        'stroke-width': 0.8,
        'stroke': this.options.grid_color,
        'stroke-miterlimit' :4,
        'stroke-dasharray': '--',
        'stroke-linecap' : 'square'
    }).toBack();
}

CandleStickGraph.prototype.drawHint = function(data){
    if (!this.hint) {
        this.hint = this.raphael.set(
            this.raphael.rect(1, 1, 230, this.bottom_offset * 1.5).attr({'stroke' : 'none', 'fill': this.options.grid_color, 'opacity':1}),
            this.raphael.text(120, this.bottom_offset - 2, 'open: 0.0000, high: 0.0000, low: 0.0000, close: 0.0000')
        );
    }

    this.hint.attr({
        'text':
            'open: '  + data.o +
            ', high: '  + data.h +
            ', low: ' + data.l +
            ', close: ' + data.c
    }).show()

    var bbox  = this.hint[1].getBBox();
    this.hint[1].attr('x', bbox.width / 2 + 6 );
    this.hint[0].attr('width', bbox.width + 12 );

}

CandleStickGraph.prototype.hideHint = function(){
    this.hint&&this.hint.hide();
}

CandleStickGraph.prototype.drawCandle = function(offset, width, open, high, low, close) {
    
  //    alert(high+" "+low+" "+close);

  var i, rc = this.raphael.set(),
        y_high = this.getYOffsetByVal(high),
        y_open = this.getYOffsetByVal(open),
        y_low = this.getYOffsetByVal(low, true),
        y_close = this.getYOffsetByVal(close, true)
    //
    if (open > close) {
        i = y_close;
        y_close = y_open;
        y_open = i;
    }

    // Фитиль wick
    if( (y_high != y_low) && ( y_high >= y_open && y_low <= y_close )) {
        rc.push(
            this.raphael
                .path(['M', offset + width / 2, y_high, 'V', y_low])
                .attr({'stroke': '#333333', 'stroke-width':1})
        );
    }

    // candle
    i = y_open - y_close;

    rc.push(
        this.raphael
            .rect(offset + .5, y_close + .5, width - 1, i <= 0 ? 1 : i )
            .attr(
                (open > close)
                    ? {
                        'fill' : this.options.price_down_color,
                        'stroke' : '#333333',
                        'stroke-width' : 1
                    }
                    : {
                        'fill': this.options.price_up_color,
                        'stroke': '#333333',
                        'stroke-width' : 1,
                        'stroke-miterlimit':4,
                        'stroke-linecap': 'butt',
                        'stroke-linejoin' : 'miter'
                    }
            )
    );

    // Область контейнер для реакции на наведение и отображение подсказок
    var hint_rect = this.raphael
        .rect(offset + .5, 1, width - 1, this.options.height - this.bottom_offset)
        .attr({'stroke' : 'none', 'opacity' : 0, 'fill' : '#fff'});
    hint_rect.info = {
        o: open,
        h: high,
        l: low,
        c: close
    }

    var _self = this;
    hint_rect.hover(function(ev){
        _self.drawHint(this.info)
    }, function(){
        this.hideHint()
    }, hint_rect, this)

    rc.push(hint_rect);

    return rc;
}

CandleStickGraph.prototype.timestam2date = function(timestamp) {
    return new Date(timestamp * 1000);
}

CandleStickGraph.prototype.drawBottomLabel = function(left_offset, timestamp) {
    var d = this.timestam2date(timestamp),
        str = d.getHours() + ':' + (d.getMinutes() < 10 ? '0' : '') + d.getMinutes();
    return this.raphael.text(left_offset, this.options.height - Math.round(this.bottom_offset / 2), str);
}

CandleStickGraph.prototype.draw = function(data) {
    var _self = this, s = 1, freq = 4,
        left_offset = 1 + this.options.candle_width;
    //
    this.initValues(data);
    this.clear();
    this.drawGrid();
    this.drawVerticalGrid(left_offset, this.options.candle_width, freq / 2);
    //
    $.each(data, function(idx, el){
        _self.drawCandle(left_offset, _self.options.candle_width, el[1], el[2], el[3], el[4]);
        if (s++ == freq) {
            s = 1;
            _self.drawBottomLabel(left_offset, el[0]);
        }
        left_offset += _self.options.candle_width + 3.75;
    });
}

CandleStickGraph.prototype.clear = function() {
    this.hint = null;
    this.raphael.clear();
}

CandleStickGraph.prototype.destroy = function() {
    this.clear();
    this.raphael.remove();
}
