
// SPDX-License-Identifier: CC-BY-NC-SA-4.0
//
// Copyright (C) 2026 Bit by Bit Signal Processing LLC (https://bxbsp.com)
//
// This work is placed under the "Creative Commons Attribution
// NonCommercial ShareAlike 4.0 International" license, known
// by the shortened acronym "CC-BY-NC-SA-4.0".
//
// This work is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//
// A CC-BY-NC-SA-4.0 license allows you to use this work for
// noncommercial purposes so long as attribution is made to the
// original author.  Modified versions of this work may be distributed,
// but only under the same license.  For further details, see the
// Creative Commons License "CC-BY-NC-SA-4.0".
//
// You should have received a copy of the CC-BY-NC-SA-4.0 license
// along with this work. If not, see
// <https://creativecommons.org/licenses/by-nc-sa/4.0/>.
//

#include "global_draw_line.hh"


void global_draw_line(panel* pan, pointf p0, pointf p1, color c)
{
  point p0i = point_from_pointf(p0);
  point p1i = point_from_pointf(p1);


  //printf("Draw line from (%d,%d) to (%d,%d)\n", p0i.x, p0i.y, p1i.x, p1i.y);
  //fflush(stdout);
  
  if(p0i==p1i)
    {
      pan->draw_point(p0i, c);
      return;
    }

  if(p0i.x == p1i.x)
    {
      // Draw a vertical line
      
      if(p0i.x<0 || p0i.x>=pan->width)
	return;

      int ystart = p0i.y;
      if(ystart<0)
        ystart = 0;
      else if(ystart>=pan->height)
        ystart = pan->height-1;

      int yend = p1i.y;
      if(yend<0)
        yend = 0;
      else if(yend>=pan->height)
        yend = pan->height-1;

      if(p0i.x==p1i.x && p0i.y==p1i.y)
	return;

      int ys = min(ystart,yend);
      int ye = max(ystart,yend);

      point p;

      p.x = p0i.x;
      
      for(p.y = ys; p.y<=ye; p.y++)
        {
          pan->draw_point_no_boundscheck(p, c);
        }
    }
  else if(p0i.y == p1i.y)
    {
      // Draw a horizontal line

      if(p0i.y<0 || p0i.y>=pan->height)
	return;
      
      int xstart = p0i.x;
      if(xstart<0)
        xstart = 0;
      else if(xstart>=pan->width)
        xstart = pan->width-1;

      int xend = p1i.x;
      if(xend<0)
        xend = 0;
      else if(xend>=pan->width)
        xend = pan->width-1;

      if(p0i.x==p1i.x && p0i.y==p1i.y)
	return;

      point p;

      p.y = p0i.y;

      int xs = min(xstart,xend);
      int xe = max(xstart,xend);
      
      for(p.x = xs; p.x<=xe; p.x++)
        {
          pan->draw_point_no_boundscheck(p, c);
        }
    }
  else
    {
      // Not horizontal or vertical
      
      float slope = (p1.y-p0.y) / (p1.x-p0.x);
      float invslope = 1.0 / slope;

      if(p0.x<0.0)
        {
          p0.y = slope * (0-p0.x) + p0.y;
          p0.x = 0.0;
          p0i = point_from_pointf(p0);
        }
      else if(p0.x>pan->width-1)
        {
          p0.y = slope * (pan->width-1-p0.x) + p0.y;
          p0.x = pan->width-1;
          p0i = point_from_pointf(p0);
        }

      if(p1.x<0.0)
        {
          p1.y = slope * (0-p1.x) + p1.y;
          p1.x = 0.0;
          p1i = point_from_pointf(p1);
        }
      else if(p1.x>pan->width-1)
        {
          p1.y = slope * (pan->width-1-p1.x) + p1.y;
          p1.x = pan->width-1;
          p1i = point_from_pointf(p1);
        }

      if(p0.y<0.0)
        {
          p0.x = invslope * (0-p0.y) + p0.x;
          p0.y = 0.0;
          p0i = point_from_pointf(p0);
        }
      else if(p0.y>pan->height-1)
        {
          p0.x = invslope * (pan->height-1-p0.y) + p0.x;
          p0.y = pan->height-1;
          p0i = point_from_pointf(p0);
        }

      if(p1.y<0.0)
        {
          p1.x = invslope * (0-p1.y) + p1.x;
          p1.y = 0.0;
          p1i = point_from_pointf(p1);
        }
      else if(p1.y>pan->height-1)
        {
          p1.x = invslope * (pan->height-1-p1.y) + p1.x;
          p1.y = pan->height-1;
          p1i = point_from_pointf(p1);
        }

      //printf("Draw line corrected to from (%d,%d) to (%d,%d) to keep within bounds (%d,%d)\n", p0i.x, p0i.y, p1i.x, p1i.y, width, height);

      // Corrections to endpoints won't work if line doesn't intersect the screen.  In that case, don't draw anything.
      if(p0i.x<0 || p0i.x>=pan->width || p0i.y<0 || p0i.y>=pan->height)
	return;
      if(p1i.x<0 || p1i.x>=pan->width || p1i.y<0 || p1i.y>=pan->height)
	return;
      if(p0i.x==p1i.x && p0i.y==p1i.y)
	return;
      
      if(slope > -1 && slope < 1)
        {
          // line is closer to horizontal

          int xstart    = min(p0i.x, p1i.x);
          int xend      = max(p0i.x, p1i.x);

          point p;
          
          for(p.x = xstart; p.x<=xend; p.x++)
            {
              float ypos      = slope * (p.x - p0.x) + p0.y;
              float ypostrunc = floor(ypos);
              float frac      = ypos-ypostrunc;
              
              p.y = (int)ypostrunc;

	      if(p.y<0)
		p.y=0;
	      if(p.y>=pan->height)
		p.y=pan->height-1;

              color bg = pan->get_point_no_boundscheck(p);              
              color cc = scale(c, bg, sqrt(1.0-frac));

              pan->draw_point_no_boundscheck(p, cc);

              p.y++;

              if(p.y < pan->height)
                {
                  color bg = pan->get_point_no_boundscheck(p);
                  color cc = scale(c, bg, sqrt(frac));
                  
                  pan->draw_point_no_boundscheck(p, cc);
                }
            }
        }
      else
        {
          // line is closer to vertical

          int ystart    = min(p0i.y, p1i.y);
          int yend      = max(p0i.y, p1i.y);

          point p;
          
          for(p.y = ystart; p.y<=yend; p.y++)
            {
              float xpos      = invslope * (p.y - p0.y) + p0.x;
              float xpostrunc = floor(xpos);
              float frac      = xpos-xpostrunc;

              p.x = (int)xpostrunc;

	      if(p.x<0)
		p.x=0;
	      if(p.x>=pan->width)
		p.x=pan->width-1;

              color bg = pan->get_point_no_boundscheck(p);              
              color cc = scale(c, bg, sqrt(1.0-frac));
              
              pan->draw_point_no_boundscheck(p, cc);

              p.x++;

              if(p.x < pan->width)
                {
                  color bg = pan->get_point_no_boundscheck(p);
                  color cc = scale(c, bg, sqrt(frac));
                  
                  pan->draw_point_no_boundscheck(p, cc);
                }
            }
        }
    }

  //  fflush(stdout);
}





void global_draw_line_horizontal(panel* pan, point p0i, point p1i, color c)
{
  if(p0i.y!=p1i.y)
    {
      printf("Error: in draw_line_horizontal: line is not horizontal!\n");
      abort();
    }
    
  if(p0i.x==p1i.x)
    {
      pan->draw_point(p0i, c);
      return;
    }

  // Draw a horizontal line

  if(p0i.y<0 || p0i.y>=pan->height)
    return;  // off screen

  int xs = min(p0i.x, p1i.x);
  int xe = max(p0i.x, p1i.x);

  if(xe<0 || xs>pan->width-1)
    return;
  
  if(xs < 0)
    xs = 0;

  if(xe >= pan->width)
    xe = pan->width-1;
  
  point p;
  
  p.y = p0i.y;
    
  for(p.x = xs; p.x<=xe; p.x++)
    {
      pan->draw_point_no_boundscheck(p, c);
    }
}


void global_draw_line_vertical(panel* pan, point p0i, point p1i, color c)
{
  if(p0i.x!=p1i.x)
    {
      printf("Error: in draw_line_vertical: line is not vertical!\n");
      abort();
    }
    
  if(p0i.y==p1i.y)
    {
      pan->draw_point(p0i, c);
      return;
    }

  // Draw a vertical line
      
  if(p0i.x<0 || p0i.x>=pan->width)
    return;  // off screen

  int ys = min(p0i.y, p1i.y);
  int ye = max(p0i.y, p1i.y);

  if(ye<0 || ys>pan->height-1)
    return;
  
  if(ys < 0)
    ys = 0;

  if(ye >= pan->height)
    ye = pan->height-1;

  point p;

  p.x = p0i.x;
      
  for(p.y = ys; p.y<=ye; p.y++)
    {
      pan->draw_point_no_boundscheck(p, c);
    }
}


//
// This is line algorithm 7 from test_lines.cc.  The best/fastest.
//
void global_draw_line_known_background(panel* pan, pointf p0, pointf p1, color c, color bg)
{
  point p0i = point_from_pointf(p0);
  point p1i = point_from_pointf(p1);

  if(p0i.x==p1i.x)
    {
      pan->draw_line_vertical(p0i, p1i, c);
      return;
    }
  else if(p0i.y==p1i.y)
    {
      pan->draw_line_horizontal(p0i, p1i, c);
      return;
    }
 
 
  float slope    = (p1.y-p0.y) / (p1.x-p0.x);

  //
  // Put p0 and p1 in proper x bounds
  //
  if(p0.x<0)
    {
      // bring p0.x in to 0.  Order matters
      p0.y -= p0.x * slope;
      p0.x = 0;
    }
  else if(p0.x>pan->width-2)
    {
      // bring p0.x in to width-1.  Order matters
      p0.y += (pan->width-2 - p0.x) * slope;
      p0.x = pan->width-2;
    }

  if(p1.x<0)
    {
      // bring p1.x in to 0.  Order matters
      p1.y -= p1.x * slope;
      p1.x = 0;
    }
  else if(p1.x>pan->width-2)
    {
      // bring p1.x in to width-1.  Order matters
      p1.y += (pan->width-2 - p1.x) * slope;
      p1.x = pan->width-2;
    }

  // Now that we're in proper x bounds, line is off the screen
  // if both points are off in the same direction in y.
  if( (p0.y<0 && p1.y<0) || (p0.y>pan->height-1&& p1.y>pan->height-1) )
    return;

  //
  // Put p0 and p1 in proper y bounds
  //
  if(p0.y<0)
    {
      // bring p0.x in to 0.  Order matters
      float invslope = 1.0/slope;
      p0.x -= p0.y * invslope;
      p0.y = 0;
    }
  else if(p0.y>pan->height-2)
    {
      // bring p0.x in to height-1.  Order matters
      float invslope = 1.0/slope;
      p0.x += (pan->height-2 - p0.y) * invslope;
      p0.y = pan->height-2;
    }  

  if(p1.y<0)
    {
      // bring p1.x in to 0.  Order matters
      float invslope = 1.0/slope;
      p1.x -= p1.y * invslope;
      p1.y = 0;
    }
  else if(p1.y>pan->height-2)
    {
      // bring p0.x in to height-1.  Order matters
      float invslope = 1.0/slope;
      p1.x += (pan->height-2 - p1.y) * invslope;
      p1.y = pan->height-2;
    }


  float adx = fabs(p1.x-p0.x);
  float ady = fabs(p1.y-p0.y);

  float denom = (adx>ady) ? adx : ady;


  if(p0.x<0 || p1.x<0 || p0.x>pan->width-1 || p1.x>pan->width-1)
    return;

  if(p0.y<0 || p1.y<0 || p0.y>pan->height-1 || p1.y>pan->height-1)
    return;

  if(denom==0.0)
    return;
  
  uint32_t curx = p0.x * 65536;
  uint32_t cury = p0.y * 65536;

  int32_t dx = (p1.x-p0.x) * 65536 / denom;  // One of these two is 1.0 (i.e. 65536)
  int32_t dy = (p1.y-p0.y) * 65536 / denom;

  //
  // Caching here helps with large numbers of small lines.
  // it's not thread-safe, but all drawing is done from
  // a single thread.
  //
  static color last   = BLACK;
  static color lastbg = BLACK;
  static color table1[16] = { BLACK };
  static color table2[16] = { BLACK };

  if(c!=last || bg!=lastbg)
    {
      last = c;
      lastbg = bg;
      
      for(int i=0; i<16; i++)
	{
	  float a = i/16.0;
	  table1[i] = scale(c, bg, sqrt(1.0-a));
	  table2[i] = scale(c, bg, sqrt(a));
	}
    }
  
  // Draw the line.


  if(adx>ady)
    {
      int xend      = p1.x;
      int yend      = pan->height-2;

      for(;;)
	{
	  uint16_t x = curx>>16;
	  uint16_t y = cury>>16;
	  uint16_t a = (cury>>12)&15;

	  if(y>=0 && y<=yend)
	    {
	      point pp0(x, y);
	      color c0  = table1[a];
	      pan->draw_point_no_boundscheck(pp0, c0);

	      point pp1(x, y+1);
	      color c1  = table2[a];
	      pan->draw_point_no_boundscheck(pp1, c1);
	    }
	  
	  if(x==xend)
	    break;

	  curx += dx;
	  cury += dy;
	}
    }
  else
    {
      int xend      = pan->width-2;
      int yend      = p1.y;
      
      for(;;)
	{
	  uint16_t x = curx>>16;
	  uint16_t y = cury>>16;
	  uint16_t a = (curx>>12)&15;

	  if(x>=0 && x<=xend)
	    {
	      point pp0(x, y);
	      color c0  = table1[a];
	      pan->draw_point_no_boundscheck(pp0, c0);

	      point pp1(x+1, y);
	      color c1  = table2[a];
	      pan->draw_point_no_boundscheck(pp1, c1);
	    }
	  
	  if(y==yend)
	    break;

	  curx += dx;
	  cury += dy;
	}
    }
}
