require_relative '../../../spec_helper'
require_relative '../../../../app/controllers/carto/api/layer_presenter'
require_relative '../../api/json/layer_presenter_shared_examples'

describe "Carto::Api::LayersController - Layer Model" do
  it_behaves_like 'layer presenters', Carto::Api::LayerPresenter, ::Layer
end

describe "Carto::Api::LayersController - Carto::Layer" do
  it_behaves_like 'layer presenters', Carto::Api::LayerPresenter, Carto::Layer
end

describe Carto::Api::LayerPresenter do
  def presenter_with_style_properties(layer)
    Carto::Api::LayerPresenter.new(layer, with_style_properties: true)
  end

  describe 'wizard_properties migration to style_properties' do
    def wizard_properties(type: 'polygon', properties: {})
      { "type" => type, 'properties' => properties }
    end

    def build_layer_with_wizard_properties(properties)
      build(:carto_layer, options: { 'wizard_properties' => properties })
    end

    it "doesn't autogenerate `style_properties` if `wizard_properties` isn't present but it's not requested" do
      layer = build_layer_with_wizard_properties(wizard_properties)
      poro_options = Carto::Api::LayerPresenter.new(layer).to_poro['options']
      poro_options['wizard_properties'].should_not be_nil
      poro_options['style_properties'].should be_nil
    end

    it "autogenerates `style_properties` based on `wizard_properties` if it isn't present and it's requested" do
      layer = build_layer_with_wizard_properties(wizard_properties)
      poro_options = presenter_with_style_properties(layer).to_poro['options']
      poro_options['wizard_properties'].should_not be_nil
      style_properties = poro_options['style_properties']
      style_properties.should_not be_nil
      style_properties['autogenerated'].should be_true
    end

    it "autogenerates `style_properties` based on `wizard_properties` if it is present but autogenerated" do
      layer = build_layer_with_wizard_properties(wizard_properties)
      layer.options['style_properties'] = { 'autogenerated' => true, 'wadus' => 'wadus' }
      poro_options = presenter_with_style_properties(layer).to_poro['options']
      poro_options['wizard_properties'].should_not be_nil
      style_properties = poro_options['style_properties']
      style_properties.should_not be_nil
      style_properties['autogenerated'].should be_true
      style_properties['type'].should_not be_nil
      style_properties['wadus'].should be_nil
    end

    it "doesn't autogenerate `style_properties` based on `wizard_properties` if it is present and not autogenerated" do
      layer = build_layer_with_wizard_properties(wizard_properties)
      layer.options['style_properties'] = { 'autogenerated' => false, 'wadus' => 'wadus' }
      poro_options = presenter_with_style_properties(layer).to_poro['options']
      poro_options['wizard_properties'].should_not be_nil
      style_properties = poro_options['style_properties']
      style_properties.should_not be_nil
      style_properties['autogenerated'].should be_false
      style_properties['wadus'].should eq 'wadus'
    end

    it "doesn't autogenerate `style_properties` if `wizard_properties` is not present or is empty" do
      layer = build_layer_with_wizard_properties(wizard_properties)
      layer.options.delete('wizard_properties')
      poro_options = presenter_with_style_properties(layer).to_poro['options']
      poro_options['wizard_properties'].should be_nil
      style_properties = poro_options['style_properties']
      style_properties.should be_nil

      layer = build_layer_with_wizard_properties(wizard_properties)
      layer.options['wizard_properties'] = nil
      poro_options = presenter_with_style_properties(layer).to_poro['options']
      poro_options['wizard_properties'].should be_nil
      style_properties = poro_options['style_properties']
      style_properties.should be_nil

      layer = build_layer_with_wizard_properties({})
      poro_options = presenter_with_style_properties(layer).to_poro['options']
      poro_options['wizard_properties'].should_not be_nil
      style_properties = poro_options['style_properties']
      style_properties.should be_nil
    end

    it "doesn't autogenerate `style_properties` if type is not mapped" do
      unknown_type = 'wadus'
      layer = build_layer_with_wizard_properties('type' => unknown_type)
      poro_options = presenter_with_style_properties(layer).to_poro['options']
      poro_options['wizard_properties']['type'].should eq unknown_type
      poro_options['style_properties'].should be_nil
    end

    describe 'options migration' do
      it 'migrates tile_style_custom to cartocss_custom (defaults to false)' do
        layer = build_layer_with_wizard_properties(wizard_properties(properties: {}))
        layer.options.delete('tile_style_custom')
        presenter_with_style_properties(layer).to_poro['options']['cartocss_custom'].should eq false
        layer.options['tile_style_custom'] = true
        presenter_with_style_properties(layer).to_poro['options']['cartocss_custom'].should eq true
        layer.options['tile_style_custom'] = false
        presenter_with_style_properties(layer).to_poro['options']['cartocss_custom'].should eq false
      end

      it 'migrates tile_style_history to cartocss_history (defaults to [])' do
        layer = build_layer_with_wizard_properties(wizard_properties(properties: {}))
        layer.options.delete('tile_style_history')
        presenter_with_style_properties(layer).to_poro['options']['cartocss_history'].should eq []
        layer.options['tile_style_history'] = ['wadus', 'wadus2']
        presenter_with_style_properties(layer).to_poro['options']['cartocss_history'].should eq ['wadus', 'wadus2']
        layer.options['tile_style_history'] = []
        presenter_with_style_properties(layer).to_poro['options']['cartocss_history'].should eq []
      end
    end

    describe 'simple' do
      it 'is generated from several types' do
        types_generating_simple = %w(polygon bubble choropleth category)
        types_generating_simple.each do |type_generating_simple|
          layer = build_layer_with_wizard_properties('type' => type_generating_simple)
          poro_options = presenter_with_style_properties(layer).to_poro['options']
          poro_options['wizard_properties']['type'].should eq type_generating_simple
          style_properties = poro_options['style_properties']
          style_properties['type'].should eq 'simple'
        end
      end

      it 'does not have animated' do
        layer = build_layer_with_wizard_properties(wizard_properties(properties: {}))
        poro_options = presenter_with_style_properties(layer).to_poro['options']
        animated = poro_options['style_properties']['properties']['animated']
        animated.should be_nil
      end

      it 'has defaults for labels' do
        layer = build_layer_with_wizard_properties(wizard_properties(properties: {}))
        poro_options = presenter_with_style_properties(layer).to_poro['options']
        labels = poro_options['style_properties']['properties']['labels']
        labels.should_not be_nil

        labels['enabled'].should eq false
        labels['attribute'].should be_nil
        labels['font'].should eq 'DejaVu Sans Book'
        fill = labels['fill']
        fill['size']['fixed'].should eq 10
        fill_color = fill['color']
        fill_color['fixed'].should eq '#000'
        fill_color['opacity'].should eq 1
        halo = labels['halo']
        halo['size']['fixed'].should eq 1
        halo_color = halo['color']
        halo_color['fixed'].should eq '#111'
        halo_color['opacity'].should eq 1
        labels['offset'].should eq(-10)
        labels['overlap'].should eq true
        labels['placement'].should eq 'point'
      end

      it 'defaults missing labels attributes' do
        properties = {
          "text-face-name" => "whatever"
        }
        layer = build_layer_with_wizard_properties(wizard_properties(properties: properties))
        poro_options = presenter_with_style_properties(layer).to_poro['options']
        labels = poro_options['style_properties']['properties']['labels']
        labels.should_not be_nil

        labels['enabled'].should eq false
        labels['font'].should eq 'whatever'

        labels['attribute'].should be_nil
        fill = labels['fill']
        fill['size']['fixed'].should eq 10
        fill_color = fill['color']
        fill_color['fixed'].should eq '#000'
        fill_color['opacity'].should eq 1
        halo = labels['halo']
        halo['size']['fixed'].should eq 1
        halo_color = halo['color']
        halo_color['fixed'].should eq '#111'
        halo_color['opacity'].should eq 1
        labels['offset'].should eq(-10)
        labels['overlap'].should eq true
        labels['placement'].should eq 'point'
      end
    end

    describe 'wizard migration' do
      COLOR = '#fabada'.freeze
      COLOR_1 = "#FACADA".freeze
      COLOR_2 = "#TACADA".freeze
      OPACITY = 0.3

      describe 'polygon' do
        describe 'polygon' do
          let(:marker_width) { 10 }
          let(:polygon_wizard_properties) do
            {
              "type" => "polygon",
              "properties" =>
                {
                  "marker-width" => marker_width,
                  "marker-fill" => "#FF6600",
                  "marker-opacity" => 0.9,
                  "marker-allow-overlap" => true,
                  "marker-placement" => "point",
                  "marker-type" => "ellipse",
                  "marker-line-width" => 1,
                  "marker-line-color" => "#FFF",
                  "marker-line-opacity" => 1,
                  "marker-comp-op" => "none",
                  "text-name" => "None",
                  "text-face-name" => "DejaVu Sans Book",
                  "text-size" => 10,
                  "text-fill" => "#000",
                  "text-halo-fill" => "#FFF",
                  "text-halo-radius" => 1,
                  "text-dy" => -10,
                  "text-allow-overlap" => true,
                  "text-placement-type" => "dummy",
                  "text-label-position-tolerance" => 0,
                  "text-placement" => "point",
                  "geometry_type" => "point"
                }
            }
          end

          it 'sets fill size from marker-width' do
            layer = build_layer_with_wizard_properties(polygon_wizard_properties)
            options = presenter_with_style_properties(layer).to_poro['options']
            properties = options['style_properties']['properties']
            fill_size = properties['fill']['size']
            fill_size['fixed'].should eq marker_width
          end

          it 'ignores property' do
            polygon_wizard_properties['properties']['property'] = 'wadus_property'
            layer = build_layer_with_wizard_properties(polygon_wizard_properties)
            options = presenter_with_style_properties(layer).to_poro['options']

            properties = options['style_properties']['properties']
            JSON.dump(properties).should_not include 'wadus_property'
          end
        end

        describe 'polygon-fill, marker-fill become "color fill" structure' do
          it 'setting opacity 1 if unknown' do
            %w(polygon-fill marker-fill).each do |property|
              properties = { property => COLOR }
              layer = build_layer_with_wizard_properties(wizard_properties(properties: properties))
              options = presenter_with_style_properties(layer).to_poro['options']

              options['wizard_properties']['properties'][property].should eq COLOR

              fill_color = options['style_properties']['properties']['fill']['color']
              fill_color.should eq('fixed' => COLOR, 'opacity' => 1)
            end
          end

          it 'setting related opacity if known' do
            %w(polygon marker).each do |property|
              properties = { "#{property}-fill" => COLOR, "#{property}-opacity" => OPACITY }
              layer = build_layer_with_wizard_properties(wizard_properties(properties: properties))
              options = presenter_with_style_properties(layer).to_poro['options']

              options['wizard_properties']['properties']["#{property}-fill"].should eq COLOR

              fill_color = options['style_properties']['properties']['fill']['color']
              fill_color.should eq('fixed' => COLOR, 'opacity' => OPACITY)
            end
          end
        end
      end

      describe 'cluster' do
        let(:query_wrapper) { "with meta ... <%= sql %> ..." }
        let(:cluster_options) do
          {
            "query_wrapper" => query_wrapper,
            "wizard_properties" => {
              "type" => "cluster",
              "properties" => { "method" => "3 Buckets", "marker-fill" => "#FD8D3C" }
            }
          }
        end
        before(:each) do
          layer = build(:carto_layer, options: cluster_options)
          @options = presenter_with_style_properties(layer).to_poro['options']
          @properties = @options['style_properties']['properties']
        end

        it 'sets type simple and cartocss_custom to true' do
          @options['style_properties']['type'].should eq 'simple'
          @options['cartocss_custom'].should eq true
        end

        it 'sets query_wrapper at sql_wrap' do
          @options['sql_wrap'].should eq query_wrapper
        end

        it 'ignores property' do
          cluster_options['wizard_properties']['properties']['property'] = 'wadus_property'
          layer = build(:carto_layer, options: cluster_options)
          options = presenter_with_style_properties(layer).to_poro['options']
          properties = options['style_properties']['properties']
          JSON.dump(properties).should_not include 'wadus_property'
        end
      end

      describe 'bubble' do
        let(:property) { "actor_foll" }
        let(:quantile_function) { "Quantile" }
        let(:qfunction) { quantile_function }
        let(:radius_min) { 10 }
        let(:radius_max) { 25 }
        let(:marker_comp_op) { "multiply" }
        let(:bubble_wizard_properties) do
          {
            "type" => "bubble",
            "properties" =>
              {
                "property" => property,
                "qfunction" => qfunction,
                "radius_min" => radius_min,
                "radius_max" => radius_max,
                "marker-fill" => COLOR,
                "marker-opacity" => OPACITY,
                "marker-line-width" => 1,
                "marker-line-color" => "#FFF",
                "marker-line-opacity" => 1,
                "zoom" => 4,
                "geometry_type" => "point",
                "text-placement-type" => "simple",
                "text-label-position-tolerance" => 10,
                "marker-comp-op" => marker_comp_op
              }
          }
        end

        before(:each) do
          layer = build_layer_with_wizard_properties(bubble_wizard_properties)
          options = presenter_with_style_properties(layer).to_poro['options']
          @properties = options['style_properties']['properties']
          @fill_color = @properties['fill']['color']
          @fill_size = @properties['fill']['size']
        end

        it 'marker-comp-op becomes blending' do
          expect(@properties).to include('blending' => marker_comp_op)
        end

        describe 'fill size' do
          it 'groups radius_min, radius_max into fill size range' do
            expect(@fill_size).to include('range' => [radius_min, radius_max])
          end

          it 'property becomes attribute' do
            expect(@fill_size).to include('attribute' => property)
          end

          it 'bins is set to 10' do
            expect(@fill_size).to include('bins' => 10)
          end

          it 'qfunction becomes quantification, and Quantile is translated to quantiles' do
            expect(@fill_size).to include('quantification' => 'quantiles')
          end

          it 'does not include animated' do
            expect(@properties['animated']).to be_nil
          end
        end

        describe 'fill color' do
          it 'takes fixed color and opacity from marker-*' do
            expect(@fill_color).to include('fixed' => COLOR, 'opacity' => OPACITY)
          end
        end
      end

      describe 'choropleth' do
        let(:property) { "actor_foll" }
        let(:number_of_buckets) { 7 }
        let(:qfunction) { "Quantile" }
        let(:color_ramp) { "red" }
        let(:choropleth_wizard_properties) do
          {
            "type" => "choropleth",
            "properties" =>
              {
                "property" => property,
                "method" => "#{number_of_buckets} Buckets",
                "qfunction" => qfunction,
                "color_ramp" => color_ramp,
                "marker-opacity" => OPACITY,
                "marker-width" => 10,
                "marker-allow-overlap" => true,
                "marker-placement" => "point",
                "marker-type" => "ellipse",
                "marker-line-width" => 1,
                "marker-line-color" => "#FFF",
                "marker-line-opacity" => 1,
                "marker-comp-op" => "none",
                "zoom" => 4,
                "geometry_type" => "point"
              }
          }
        end

        before(:each) do
          layer = build_layer_with_wizard_properties(choropleth_wizard_properties)
          options = presenter_with_style_properties(layer).to_poro['options']
          @fill_color = options['style_properties']['properties']['fill']['color']
          @fill_size = options['style_properties']['properties']['fill']['size']
        end

        describe 'fill' do
          it 'has fixed size 10 by default' do
            expect(@fill_size).to include('fixed' => 10)
          end

          it 'transform color_ramp to color array in range' do
            # Commented because this might change soon
            # expect(@fill_color).to include('range' => "['#FFEDA0', '#FEB24C', '#F03B20']")
            expect(@fill_color).to include('range' => color_ramp)
          end

          it 'property becomes attribute' do
            expect(@fill_color).to include('attribute' => property)
          end

          it 'method generates bins' do
            expect(@fill_color).to include('bins' => number_of_buckets)
          end

          it 'qfunction becomes quantification, and Quantile is translated to quantiles' do
            expect(@fill_color).to include('quantification' => 'quantiles')
          end

          it 'takes opacity from marker-* or polygon-*' do
            expect(@fill_color).to include('opacity' => OPACITY)
          end
        end
      end

      describe 'category' do
        let(:property) { "aforo" }
        let(:opacity) { 0.456 }
        let(:marker_line_width) { 1 }
        let(:marker_line_color) { "#FFF" }
        let(:marker_line_opacity) { 0.7 }
        let(:title_1) { 100 }
        let(:title_2) { 200 }
        let(:line_width) { 0.43 }
        let(:line_color) { "#FFA" }
        let(:line_opacity) { 0.91 }
        let(:category_wizard_properties) do
          {
            "type" => "category",
            "properties" => {
              "property" => property,
              "zoom" => "15",
              "geometry_type" => 'point',
              "text-placement-type" => "simple",
              "text-label-position-tolerance" => 10,
              "categories" => [
                {
                  "title" => title_1,
                  "title_type" => "number",
                  "color" => COLOR_1,
                  "value_type" => "color"
                },
                {
                  "title" => title_2,
                  "title_type" => "number",
                  "color" => COLOR_2,
                  "value_type" => "color"
                }
              ]
            }
          }
        end

        let(:marker_category_properties) do
          {
            "marker-width" => 10,
            "marker-opacity" => opacity,
            "marker-allow-overlap" => true,
            "marker-placement" => "point",
            "marker-type" => "ellipse",
            "marker-line-width" => marker_line_width,
            "marker-line-color" => marker_line_color,
            "marker-line-opacity" => marker_line_opacity
          }
        end

        let(:line_category_properties) do
          {
            "line-width" => line_width,
            "line-color" => line_color,
            "line-opacity" => line_opacity
          }
        end

        describe 'polygon geometry type' do
          before(:each) do
            properties = category_wizard_properties
            properties['properties']['geometry_type'] = 'polygon'
            properties['properties'].merge!(line_category_properties)
            layer = build_layer_with_wizard_properties(properties)
            @options = presenter_with_style_properties(layer).to_poro['options']
            @fill_color = @options['style_properties']['properties']['fill']['color']
            @fill_size = @options['style_properties']['properties']['fill']['size']
            @stroke_color = @options['style_properties']['properties']['stroke']['color']
            @stroke_size = @options['style_properties']['properties']['stroke']['size']
          end

          it 'sets "none" as default blending' do
            @options['style_properties']['properties']['blending'].should eq 'none'
          end

          it 'line-width becomes fixed stroke size' do
            expect(@stroke_size).to include('fixed' => line_width)
          end

          it 'line-color becomes fixed stroke color' do
            expect(@stroke_color).to include('fixed' => line_color)
          end

          it 'line-opacity becomes opacity' do
            expect(@stroke_color).to include('opacity' => line_opacity)
          end
        end

        describe 'point geometry type' do
          before(:each) do
            properties = category_wizard_properties
            properties['properties'].merge!(marker_category_properties)
            layer = build_layer_with_wizard_properties(properties)
            options = presenter_with_style_properties(layer).to_poro['options']
            @fill_color = options['style_properties']['properties']['fill']['color']
            @fill_size = options['style_properties']['properties']['fill']['size']
            @stroke_color = options['style_properties']['properties']['stroke']['color']
            @stroke_size = options['style_properties']['properties']['stroke']['size']
          end

          describe 'fill' do
            it 'has fill size fixed 10' do
              expect(@fill_size).to include('fixed' => 10)
            end

            it 'generates not fixed color but range from categories colors' do
              expect(@fill_color).not_to include('fixed' => anything)
              expect(@fill_color).to include('range' => [COLOR_1, COLOR_2])
            end

            it 'generates domain from titles' do
              expect(@fill_color).to include('domain' => [title_1, title_2])
            end

            it 'property becomes attribute' do
              expect(@fill_color).to include('attribute' => property)
              expect(@fill_size).not_to include('attribute' => property)
            end

            it 'marker-opacity becomes opacity' do
              expect(@fill_color).to include('opacity' => opacity)
            end

            it 'bins defaults to 10' do
              expect(@fill_color).to include('bins' => 10)
            end
          end

          describe 'stroke' do
            it 'marker-line-width becomes fixed stroke size' do
              expect(@stroke_size).to include('fixed' => marker_line_width)
            end

            it 'marker-line-color becomes fixed stroke color' do
              expect(@stroke_color).to include('fixed' => marker_line_color)
            end

            it 'marker-line-opacity becomes opacity' do
              expect(@stroke_color).to include('opacity' => marker_line_opacity)
            end
          end
        end

        describe 'line geometry type' do
          before(:each) do
            properties = category_wizard_properties
            properties['properties'].merge!(line_category_properties)
            properties['properties']['geometry_type'] = 'line'
            layer = build_layer_with_wizard_properties(properties)

            options = presenter_with_style_properties(layer).to_poro['options']
            @stroke_color = options['style_properties']['properties']['stroke']['color']
            @stroke_size = options['style_properties']['properties']['stroke']['size']
          end

          it 'generates fixed size from line_width' do
            expect(@stroke_size).to include('fixed' => line_width)
          end

          it 'generates color range from categories colors' do
            expect(@stroke_color).to include('range' => [COLOR_1, COLOR_2])
          end

          it 'property becomes attribute' do
            expect(@stroke_color).to include('attribute' => property)
            expect(@stroke_size).not_to include('attribute' => property)
          end

          it 'line-width becomes fixed stroke size' do
            expect(@stroke_size).to include('fixed' => line_width)
          end

          it 'line-color becomes fixed stroke color' do
            expect(@stroke_color).to include('fixed' => line_color)
          end

          it 'line-opacity becomes opacity' do
            expect(@stroke_color).to include('opacity' => line_opacity)
          end

          it 'bins defaults to 10' do
            expect(@stroke_color).to include('bins' => 10)
          end

          describe 'stroke' do
            let(:line_width) { 6 }
            let(:line_color) { "#0F3B82" }
            let(:line_opacity) { 0.7 }
            let(:line_comp_op) { "none" }
            let(:simple_line_wizard_properties) do
              {
                "type" => "polygon",
                "geometry_type" => "line",
                "properties" =>
                  {
                    "line-width" => line_width,
                    "line-color" => line_color,
                    "line-opacity" => line_opacity,
                    "line-comp-op" => line_comp_op,
                    "geometry_type" => "line"
                  }
              }
            end

            before(:each) do
              layer = build_layer_with_wizard_properties(simple_line_wizard_properties)
              options = presenter_with_style_properties(layer).to_poro['options']
              @properties = options['style_properties']['properties']
              @stroke = @properties['stroke']
              @stroke_color = @stroke['color']
              @stroke_size = @stroke['size']
            end

            it 'generates stroke' do
              expect(@stroke_size).to include('fixed' => line_width)
              expect(@stroke_color).to include('fixed' => line_color)
              expect(@stroke_color).to include('opacity' => line_opacity)
              expect(@properties).to include('blending' => line_comp_op)
            end
          end
        end
      end

      shared_examples_for 'torque wizard family' do
        describe 'animated' do
          it 'property becomes attribute' do
            expect(@animated).to include('attribute' => property)
          end

          it 'property becomes attribute only at animated' do
            expect(@fill['color']).not_to include('attribute' => property)
          end

          it 'torque-duration becomes duration' do
            expect(@animated).to include('duration' => torque_duration)
          end

          it 'torque-frame-count becomes steps' do
            expect(@animated).to include('steps' => torque_frame_count)
          end

          it 'torque-resolution becomes resolution' do
            expect(@animated).to include('resolution' => torque_resolution)
          end

          it 'torque-trails becomes trails' do
            expect(@animated).to include('trails' => torque_trails)
          end

          it "doesn't include redundant animated enabled option" do
            expect(@animated).not_to include('enabled')
          end
        end
      end

      shared_examples_for 'torque blend wizard family' do
        describe 'torque-blend-mode' do
          it 'becomes blending' do
            expect(@properties).to include('blending' => torque_blend_mode)
          end
        end
      end

      shared_examples_for 'torque animated family' do
        it_behaves_like 'torque wizard family'

        it 'type is animated' do
          expect(@style).to include('type' => 'animation')
        end

        it 'generates a timeseries widget' do
          @layer.widgets.where(type: 'time-series').any?
        end
      end

      describe 'torque' do
        it_behaves_like 'torque animated family'
        it_behaves_like 'torque blend wizard family'

        let(:torque_blend_mode) { "lighter" }
        let(:property) { "fecha_date" }
        let(:marker_width) { 6 }
        let(:torque_cumulative) { false }
        let(:torque_duration) { 30 }
        let(:torque_frame_count) { 256 }
        let(:torque_resolution) { 2 }
        let(:torque_trails) { 3 }
        let(:torque_wizard_properties) do
          {
            "type" => "torque",
            "properties" =>
              {
                "torque-cumulative" => torque_cumulative,
                "property" => property,
                "marker-type" => "ellipse",
                "layer-type" => "torque",
                "marker-width" => marker_width,
                "marker-fill" => "#0F3B82",
                "marker-opacity" => 0.9,
                "marker-line-width" => 0,
                "marker-line-color" => "#FFF",
                "marker-line-opacity" => 1,
                "torque-duration" => torque_duration,
                "torque-frame-count" => torque_frame_count,
                "torque-blend-mode" => torque_blend_mode,
                "torque-trails" => torque_trails,
                "torque-resolution" => torque_resolution,
                "zoom" => 15,
                "geometry_type" => "point"
              }
          }
        end

        before(:each) do
          @layer = build_layer_with_wizard_properties(torque_wizard_properties)
          options = presenter_with_style_properties(@layer).to_poro['options']
          @style = options['style_properties']
          @properties = @style['properties']
          @animated = @properties['animated']
          @fill = @properties['fill']
          @fill_color = @fill['color']
          @fill_size = @fill['size']
        end

        it 'torque-cumulative becomes overlap' do
          expect(@animated).to include('overlap' => torque_cumulative)
        end

        it 'marker-width becomes fill size fixed' do
          expect(@fill_size).to include('fixed' => marker_width)
        end

        describe 'torque-blend-mode' do
          it 'turns source-over into src-over' do
            source_over_torque_wizard_properties = torque_wizard_properties
            source_over_torque_wizard_properties['properties']['torque-blend-mode'] = 'source-over'
            layer = build_layer_with_wizard_properties(source_over_torque_wizard_properties)
            options = presenter_with_style_properties(layer).to_poro['options']
            options['style_properties']['properties']['blending'].should eq 'src-over'
          end
        end
      end
    end

    describe 'torque cat' do
      it_behaves_like 'torque animated family'
      it_behaves_like 'torque blend wizard family'

      let(:torque_blend_mode) { "lighter" }
      let(:property) { "fecha_date" }
      let(:property_cat) { "aforo" }
      let(:torque_duration) { 30 }
      let(:torque_frame_count) { 256 }
      let(:torque_resolution) { 2 }
      let(:torque_trails) { 3 }
      let(:torque_cat_wizard_properties) do
        {
          "type" => "torque",
          "properties" =>
            {
              "property" => property,
              "marker-type" => "ellipse",
              "layer-type" => "torque",
              "property_cat" => property_cat,
              "marker-width" => 6,
              "marker-fill" => "#0F3B82",
              "marker-opacity" => 0.9,
              "marker-line-width" => 0,
              "marker-line-color" => "#FFF",
              "marker-line-opacity" => 1,
              "torque-duration" => torque_duration,
              "torque-frame-count" => torque_frame_count,
              "torque-blend-mode" => torque_blend_mode,
              "torque-trails" => torque_trails,
              "torque-resolution" => torque_resolution,
              "zoom" => 15,
              "geometry_type" => "point",
              "categories" => [
                {
                  "title" => 100,
                  "title_type" => "number",
                  "color" => COLOR_1,
                  "value_type" => "color"
                },
                {
                  "title" => 200,
                  "title_type" => "number",
                  "color" => COLOR_2,
                  "value_type" => "color"
                }
              ]
            }
        }
      end

      before(:each) do
        @layer = build_layer_with_wizard_properties(torque_cat_wizard_properties)
        options = presenter_with_style_properties(@layer).to_poro['options']
        @style = options['style_properties']
        @properties = @style['properties']
        @animated = @properties['animated']
        @fill = @properties['fill']
        @fill_color = @fill['color']
        @fill_size = @fill['size']
      end

      it 'generates color range from categories colors' do
        expect(@fill_color).to include('range' => [COLOR_1, COLOR_2])
      end

      it 'generates color range based on attribute' do
        expect(@fill_color).to include('attribute' => 'aforo')
      end
    end

    describe 'density' do
      let(:query_wrapper) { "with meta ... <%= sql %> ..." }
      let(:color_ramp) { "red" }
      let(:polygon_size) { 15 }
      let(:density_wizard_properties) do
        {
          "type" => "density",
          "properties" =>
            {
              "geometry_type" => "point",
              "method" => "5 Buckets",
              "color_ramp" => color_ramp,
              "polygon-opacity" => 0.8,
              "line-width" => 0.5,
              "line-color" => "#FFF",
              "line-opacity" => 1,
              "polygon-size" => polygon_size,
              "polygon-comp-op" => "none",
              "zoom" => 15
            }
        }
      end

      before(:each) do
        options = { 'query_wrapper' => query_wrapper, 'wizard_properties' => density_wizard_properties }
        layer = build(:carto_layer, options: options)
        @options = presenter_with_style_properties(layer).to_poro['options']

        @style = @options['style_properties']
        @properties = @style['properties']
        @aggregation = @properties['aggregation']
        @fill_color = @properties['fill']['color']
        @stroke = @properties['stroke']
      end

      it 'sets query_wrapper at sql_wrap' do
        @options['sql_wrap'].should eq query_wrapper
      end

      it 'maps point geometry_type to hexabins type' do
        expect(@style).to include('type' => 'hexabins')
      end

      it 'maps Rectangles geometry_type to squares type' do
        properties = density_wizard_properties
        properties['properties']['geometry_type'] = 'Rectangles'
        layer = build_layer_with_wizard_properties(properties)
        options = presenter_with_style_properties(layer).to_poro['options']
        expect(options['style_properties']).to include('type' => 'squares')
      end

      describe 'aggregation' do
        it 'has defaults' do
          expect(@aggregation).to include(
            "value" => {
              "operator" => 'COUNT',
              "attribute" => ''
            }
          )
        end

        it 'takes size from polygon-size' do
          expect(@aggregation).to include("size" => polygon_size)
        end
      end

      describe 'fill' do
        it 'transform color_ramp to color range' do
          expect(@fill_color).to include('range' => color_ramp)
        end
      end

      describe 'stroke' do
        it 'takes width from line-width' do
          expect(@stroke['size']).to include('fixed' => 0.5)
        end

        it 'takes color from line-color' do
          expect(@stroke['color']).to include('fixed' => '#FFF')
        end

        it 'takes opacity from line-opacity' do
          expect(@stroke['color']).to include('opacity' => 1)
        end
      end
    end

    describe 'heatmap' do
      shared_examples_for 'heatmap' do
        describe 'aggregation' do
          before(:each) do
            @aggregation = @style['properties']['aggregation']
          end

          it 'takes size from torque-resolution' do
            expect(@aggregation).to include('size' => torque_resolution)
          end

          it 'has COUNT for value operator and empty string for attribute' do
            expect(@aggregation['value']).to include('operator' => 'COUNT', 'attribute' => '')
          end
        end

        describe 'properties' do
          before(:each) do
            @properties = @style['properties']
          end

          describe 'fill' do
            it 'has size fixed 35' do
              expect(@fill['size']).to include('fixed' => 35)
            end

            describe 'color' do
              before(:each) do
                @color = @fill['color']
              end

              it "has attribute: points_agg, default range color array, bins: 6 and opacity from marker_opacity" do
                expect(@color).to include('attribute' => 'points_agg',
                                          'range' => ['blue', 'cyan', 'lightgreen', 'yellow', 'orange', 'red'],
                                          'bins' => 6,
                                          'opacity' => marker_opacity)
              end
            end
          end
        end
      end

      let(:heatmap_wizard_properties) do
        { "type" => "torque_heat",
          "properties" =>
            {
              "marker-width" => 35,
              "layer-type" => "torque",
              "marker-file" => "url(http://s3.amazonaws.com/com.cartodb.assets.static/alphamarker.png)",
              "image-filters" => "colorize-alpha(blue, cyan, lightgreen, yellow , orange, red)",
              "marker-opacity" => marker_opacity,
              "heat-animated" => animated,
              "torque-cumulative" => false,
              "property" => property,
              "torque-duration" => torque_duration,
              "torque-frame-count" => torque_frame_count,
              "torque-trails" => torque_trails,
              "torque-resolution" => torque_resolution,
              "zoom" => 3,
              "geometry_type" => "point",
              "text-placement-type" => "simple",
              "text-label-position-tolerance" => 10
            }
          }
      end

      before(:each) do
        @layer = build_layer_with_wizard_properties(heatmap_wizard_properties)
        options = presenter_with_style_properties(@layer).to_poro['options']

        @style = options['style_properties']
        @properties = @style['properties']
        @animated = @properties['animated']
        @fill = @properties['fill']
        @fill_color = @fill['color']
        @fill_size = @fill['size']
      end

      describe 'animated' do
        let(:torque_resolution) { 8 }
        let(:marker_opacity) { 0.4 }
        let(:torque_trails) { 2 }
        let(:torque_frame_count) { 32 }
        let(:torque_duration) { 10 }
        let(:property) { "actor_post" }
        let(:animated) { true }

        it_behaves_like 'torque animated family'
        it_behaves_like 'heatmap'
      end

      describe 'without animation' do
        let(:torque_resolution) { 8 }
        let(:marker_opacity) { 0.4 }
        let(:torque_trails) { 2 }
        let(:torque_frame_count) { 1 }
        let(:torque_duration) { 10 }
        let(:property) { "cartodb_id" }
        let(:animated) { false }

        it 'is generated from torque_heat' do
          expect(@style).to include('type' => 'heatmap')
        end

        it_behaves_like 'torque wizard family'
        it_behaves_like 'heatmap'
      end
    end

    describe 'labels' do
      describe 'without text-* properties' do
        let(:no_text_wizard_properties) do
          {
            "type" => "choropleth",
            "properties" => { "property" => 'actor_foll' }
          }
        end

        it 'generates disabled labels' do
          layer = build_layer_with_wizard_properties(no_text_wizard_properties)
          options = presenter_with_style_properties(layer).to_poro['options']
          options['style_properties']['properties']['labels']['enabled'].should be_false
        end
      end

      describe 'with text-name `None`' do
        let(:none_text_name_wizard_properties) do
          {
            "type" => "choropleth",
            "properties" =>
              {
                "text-name" => 'None',
                "text-face-name" => 'something'
              }
          }
        end

        it 'generates disabled labels' do
          layer = build_layer_with_wizard_properties(none_text_name_wizard_properties)
          options = presenter_with_style_properties(layer).to_poro['options']
          options['style_properties']['properties']['labels']['enabled'].should be_false
        end
      end

      describe 'with text-* properties' do
        let(:text_name) { "Something" }
        let(:text_face_name) { "DejaVu Sans Book" }
        let(:text_size) { 10 }
        let(:text_fill) { "#000" }
        let(:text_halo_radius) { 1 }
        let(:text_halo_fill) { "#ABC" }
        let(:text_dy) { -10 }
        let(:text_allow_overlap) { true }
        let(:text_placement_type) { "simple" }
        let(:text_wizard_properties) do
          {
            "type" => "choropleth",
            "properties" =>
              {
                "text-name" => text_name,
                "text-face-name" => text_face_name,
                "text-size" => text_size,
                "text-fill" => text_fill,
                "text-halo-fill" => text_halo_fill,
                "text-halo-radius" => text_halo_radius,
                "text-dy" => text_dy,
                "text-allow-overlap" => text_allow_overlap,
                "text-placement-type" => text_placement_type,
                "text-label-position-tolerance" => 10,
                "text-placement" => "point"
              }
          }
        end

        before(:each) do
          layer = build_layer_with_wizard_properties(text_wizard_properties)
          options = presenter_with_style_properties(layer).to_poro['options']
          @labels = options['style_properties']['properties']['labels']
        end

        it 'generates labels' do
          @labels.should_not be_nil
          @labels['enabled'].should be_true
        end

        it 'text-name generates attribute' do
          expect(@labels).to include('attribute' => text_name)
        end

        it 'text-name `None` generates `nil` attribute' do
          properties = text_wizard_properties
          properties['properties']['text-name'] = 'None'
          layer = build_layer_with_wizard_properties(properties)
          options = presenter_with_style_properties(layer).to_poro['options']
          labels = options['style_properties']['properties']['labels']
          expect(labels).to include('attribute' => nil)
        end

        it 'text-face-name generates font' do
          expect(@labels).to include('font' => text_face_name)
        end

        it 'text-dy generates offset' do
          expect(@labels).to include('offset' => text_dy)
        end

        it 'text-allow-overlap generates overlap' do
          expect(@labels).to include('overlap' => text_allow_overlap)
        end

        it 'text-placement-type generates placement' do
          expect(@labels).to include('placement' => text_placement_type)
        end

        describe 'fill' do
          before(:each) do
            @labels_fill = @labels['fill']
            @labels_fill.should_not be_nil
            @labels_fill_size = @labels_fill['size']
            @labels_fill_color = @labels_fill['color']
          end

          it 'text-size generates fill size fixed' do
            expect(@labels_fill_size).to include('fixed' => text_size)
          end

          it 'text-fill generates fill color fixed and opacity 1 if not present' do
            expect(@labels_fill_color).to include('fixed' => text_fill, 'opacity' => 1)
          end

          it 'text-fill generates fill color fixed and opacity if present' do
            text_with_opacity = text_wizard_properties
            text_with_opacity["properties"]["text-opacity"] = OPACITY
            layer = build_layer_with_wizard_properties(text_with_opacity)
            options = presenter_with_style_properties(layer).to_poro['options']
            labels_fill_color = options['style_properties']['properties']['labels']['fill']['color']
            expect(labels_fill_color).to include('fixed' => text_fill, 'opacity' => OPACITY)
          end
        end

        describe 'halo' do
          before(:each) do
            @labels_halo = @labels['halo']
            @labels_halo.should_not be_nil
            @labels_halo_size = @labels_halo['size']
            @labels_halo_color = @labels_halo['color']
          end

          it 'text-halo-radius generates halo size fixed' do
            expect(@labels_halo_size).to include('fixed' => text_halo_radius)
          end

          it 'text-halo-fill generates halo color fixed and opacity 1 if not present' do
            expect(@labels_halo_color).to include('fixed' => text_halo_fill, 'opacity' => 1)
          end

          it 'text-halo-fill generates fill color fixed and opacity if present' do
            halo_with_opacity = text_wizard_properties
            halo_with_opacity["properties"]["text-halo-opacity"] = OPACITY
            layer = build_layer_with_wizard_properties(halo_with_opacity)
            options = presenter_with_style_properties(layer).to_poro['options']
            labels_halo_color = options['style_properties']['properties']['labels']['halo']['color']
            expect(labels_halo_color).to include('fixed' => text_halo_fill, 'opacity' => OPACITY)
          end
        end
      end
    end
  end
end
