require "spec_helper"

describe ChunkyPNG::Canvas do
  subject { ChunkyPNG::Canvas.new(1, 1, ChunkyPNG::Color::WHITE) }

  it { should respond_to(:width) }
  it { should respond_to(:height) }
  it { should respond_to(:pixels) }

  describe "#initialize" do
    it "should accept a single color value as background color" do
      canvas = ChunkyPNG::Canvas.new(2, 2, "red @ 0.8")
      expect(canvas[1, 0]).to eql ChunkyPNG::Color.parse("red @ 0.8")
    end

    it "should raise an error if the color value is not understood" do
      expect { ChunkyPNG::Canvas.new(2, 2, :nonsense) }.to raise_error(ArgumentError)
    end

    it "should accept an array as initial pixel values" do
      canvas = ChunkyPNG::Canvas.new(2, 2, [1, 2, 3, 4])
      expect(canvas[0, 0]).to eql 1
      expect(canvas[1, 0]).to eql 2
      expect(canvas[0, 1]).to eql 3
      expect(canvas[1, 1]).to eql 4
    end

    it "should raise an ArgumentError if the initial array does not have the correct number of elements" do
      expect { ChunkyPNG::Canvas.new(2, 2, [1, 2, 3]) }.to raise_error(ArgumentError)
      expect { ChunkyPNG::Canvas.new(2, 2, [1, 2, 3, 4, 5]) }.to raise_error(ArgumentError)
    end

    it "should use a transparent background by default" do
      canvas = ChunkyPNG::Canvas.new(1, 1)
      expect(canvas[0, 0]).to eql ChunkyPNG::Color::TRANSPARENT
    end
  end

  describe "#dimension" do
    it "should return the dimensions as a Dimension instance" do
      expect(subject.dimension).to eql ChunkyPNG::Dimension("1x1")
    end
  end

  describe "#area" do
    it "should return the dimensions as two-item array" do
      expect(subject.area).to eql ChunkyPNG::Dimension("1x1").area
    end
  end

  describe "#include?" do
    it "should return true if the coordinates are within bounds, false otherwise" do
      # rubocop:disable Layout/SpaceInsideParens
      expect(subject.include_xy?( 0,  0)).to eql true
      expect(subject.include_xy?(-1,  0)).to eql false
      expect(subject.include_xy?( 1,  0)).to eql false
      expect(subject.include_xy?( 0, -1)).to eql false
      expect(subject.include_xy?( 0,  1)).to eql false
      expect(subject.include_xy?(-1, -1)).to eql false
      expect(subject.include_xy?(-1,  1)).to eql false
      expect(subject.include_xy?( 1, -1)).to eql false
      expect(subject.include_xy?( 1,  1)).to eql false
      # rubocop:enable Layout/SpaceInsideParens
    end

    it "should accept strings, arrays, hashes and points as well" do
      expect(subject).to include("0, 0")
      expect(subject).to_not include("0, 1")
      expect(subject).to include([0, 0])
      expect(subject).to_not include([0, 1])
      expect(subject).to include(y: 0, x: 0)
      expect(subject).to_not include(y: 1, x: 0)
      expect(subject).to include(ChunkyPNG::Point.new(0, 0))
      expect(subject).to_not include(ChunkyPNG::Point.new(0, 1))
    end
  end

  describe "#include_x?" do
    it "should return true if the x-coordinate is within bounds, false otherwise" do
      expect(subject.include_x?(0)).to eql true
      expect(subject.include_x?(-1)).to eql false
      expect(subject.include_x?(1)).to eql false
    end
  end

  describe "#include_y?" do
    it "should return true if the y-coordinate is within bounds, false otherwise" do
      expect(subject.include_y?(0)).to eql true
      expect(subject.include_y?(-1)).to eql false
      expect(subject.include_y?(1)).to eql false
    end
  end

  describe "#assert_xy!" do
    it "should not raise an exception if the coordinates are within bounds" do
      expect(subject).to receive(:include_xy?).with(0, 0).and_return(true)
      expect { subject.send(:assert_xy!, 0, 0) }.to_not raise_error
    end

    it "should raise an exception if the coordinates are out of bounds bounds" do
      expect(subject).to receive(:include_xy?).with(0, -1).and_return(false)
      expect { subject.send(:assert_xy!, 0, -1) }.to raise_error(ChunkyPNG::OutOfBounds)
    end
  end

  describe "#assert_x!" do
    it "should not raise an exception if the x-coordinate is within bounds" do
      expect(subject).to receive(:include_x?).with(0).and_return(true)
      expect { subject.send(:assert_x!, 0) }.to_not raise_error
    end

    it "should raise an exception if the x-coordinate is out of bounds bounds" do
      expect(subject).to receive(:include_y?).with(-1).and_return(false)
      expect { subject.send(:assert_y!, -1) }.to raise_error(ChunkyPNG::OutOfBounds)
    end
  end

  describe "#[]" do
    it "should return the pixel value if the coordinates are within bounds" do
      expect(subject[0, 0]).to eql ChunkyPNG::Color::WHITE
    end

    it "should assert the coordinates to be within bounds" do
      expect(subject).to receive(:assert_xy!).with(0, 0)
      subject[0, 0]
    end
  end

  describe "#get_pixel" do
    it "should return the pixel value if the coordinates are within bounds" do
      expect(subject.get_pixel(0, 0)).to eql ChunkyPNG::Color::WHITE
    end

    it "should not assert nor check the coordinates" do
      expect(subject).to_not receive(:assert_xy!)
      expect(subject).to_not receive(:include_xy?)
      subject.get_pixel(0, 0)
    end
  end

  describe "#[]=" do
    it "should change the pixel's color value" do
      expect { subject[0, 0] = ChunkyPNG::Color::BLACK }.to change { subject[0, 0] }
        .from(ChunkyPNG::Color::WHITE)
        .to(ChunkyPNG::Color::BLACK)
    end

    it "should assert the bounds of the image" do
      expect(subject).to receive(:assert_xy!).with(0, 0)
      subject[0, 0] = ChunkyPNG::Color::BLACK
    end
  end

  describe "set_pixel" do
    it "should change the pixel's color value" do
      expect { subject.set_pixel(0, 0, ChunkyPNG::Color::BLACK) }.to change { subject[0, 0] }
        .from(ChunkyPNG::Color::WHITE)
        .to(ChunkyPNG::Color::BLACK)
    end

    it "should not assert or check the bounds of the image" do
      expect(subject).to_not receive(:assert_xy!)
      expect(subject).to_not receive(:include_xy?)
      subject.set_pixel(0, 0, ChunkyPNG::Color::BLACK)
    end
  end

  describe "#set_pixel_if_within_bounds" do
    it "should change the pixel's color value" do
      expect { subject.set_pixel_if_within_bounds(0, 0, ChunkyPNG::Color::BLACK) }.to change { subject[0, 0] }
        .from(ChunkyPNG::Color::WHITE)
        .to(ChunkyPNG::Color::BLACK)
    end

    it "should not assert, but only check the coordinates" do
      expect(subject).to_not receive(:assert_xy!)
      expect(subject).to receive(:include_xy?).with(0, 0)
      subject.set_pixel_if_within_bounds(0, 0, ChunkyPNG::Color::BLACK)
    end

    it "should do nothing if the coordinates are out of bounds" do
      expect(subject.set_pixel_if_within_bounds(-1, 1, ChunkyPNG::Color::BLACK)).to be_nil
      expect(subject[0, 0]).to eql ChunkyPNG::Color::WHITE
    end
  end

  describe "#row" do
    before { @canvas = reference_canvas("operations") }

    it "should give an out of bounds exception when y-coordinate is out of bounds" do
      expect { @canvas.row(-1) }.to raise_error(ChunkyPNG::OutOfBounds)
      expect { @canvas.row(16) }.to raise_error(ChunkyPNG::OutOfBounds)
    end

    it "should return the correct pixels" do
      data = @canvas.row(0)
      expect(data.length).to eql @canvas.width
      expect(data).to eql [65535, 268500991, 536936447, 805371903, 1073807359, 1342242815, 1610678271, 1879113727, 2147549183, 2415984639, 2684420095, 2952855551, 3221291007, 3489726463, 3758161919, 4026597375]
    end
  end

  describe "#column" do
    before { @canvas = reference_canvas("operations") }

    it "should give an out of bounds exception when x-coordinate is out of bounds" do
      expect { @canvas.column(-1) }.to raise_error(ChunkyPNG::OutOfBounds)
      expect { @canvas.column(16) }.to raise_error(ChunkyPNG::OutOfBounds)
    end

    it "should return the correct pixels" do
      data = @canvas.column(0)
      expect(data.length).to eql @canvas.height
      expect(data).to eql [65535, 1114111, 2162687, 3211263, 4259839, 5308415, 6356991, 7405567, 8454143, 9502719, 10551295, 11599871, 12648447, 13697023, 14745599, 15794175]
    end
  end

  describe "#replace_canvas" do
    it "should change the dimension of the canvas" do
      expect { subject.send(:replace_canvas!, 2, 2, [1, 2, 3, 4]) }.to change { subject.dimension }
        .from(ChunkyPNG::Dimension("1x1"))
        .to(ChunkyPNG::Dimension("2x2"))
    end

    it "should change the pixel array" do
      expect { subject.send(:replace_canvas!, 2, 2, [1, 2, 3, 4]) }.to change { subject.pixels }
        .from([ChunkyPNG::Color("white")])
        .to([1, 2, 3, 4])
    end

    it "should return itself" do
      expect(subject.send(:replace_canvas!, 2, 2, [1, 2, 3, 4])).to equal(subject)
    end
  end

  describe "#inspect" do
    it "should give a string description of the canvas" do
      expect(subject.inspect).to eql "<ChunkyPNG::Canvas 1x1 [\n\t[#ffffffff]\n]>"
    end
  end
end
