/*
   Copyright 2010 Aaron J. Radke

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
package cc.drx

/**
 * A general color class and constructor
 */
object Color{
   //--constructors
   implicit object ParsableColor extends Parsable[Color]{  def apply(color:String):Color = Color(color)  }

   private def numeric(str:String):Color = Try{
      val s = if(str startsWith "#") str drop 1
              else if(str startsWith "0x") str drop 2
              else str
      s.size match {
         case 3 =>
            val r = s(0); val g = s(1); val b = s(2);
            argb(Parse[Int](s"0xFF$r$r$g$g$b$b"))
         case 4 =>
            val a = s(0); val r = s(1); val g = s(2); val b = s(3);
            argb(Parse[Int](s"0x$a$a$r$r$g$g$b$b"))
         case 6 =>
            rgb(Parse[Int](str))
         case _ =>
            argb(Parse[Int](str))
      }
   } getOrElse Black

   //TODO implement a css like `color-mod` that uses "red s(-10%)" like notation with blend and whiteness
   def apply(str:String):Color = {
      val s = str.trim
      val es = s.split("opacity")
      if(es.size == 1)
         (drxColorNames++catColorNames).getOrElse(s, //first check without changing the case
            cssColorNames.getOrElse(s.toLowerCase,  //then check the lowercase version name in the CSS names
               numeric(s) //then just convert it as a number
            )
         )
      else if (es.size == 2)
         Color(es(0)) opacity Parse(es(1))
      else Black
   }
   def apply(argb:Int):Color = new Color(argb)

   def rgb(r:Int,g:Int,b:Int):Color = new Color(0xFF000000 | r<<16 | g<<8 | b)
   def rgb(r:Int,g:Int,b:Int,a:Int):Color = new Color(a<<24 | r<<16 | g<<8 | b)
   def rgb(rgb:Int,a:Int):Color = new Color(a<<24 | (rgb & 0x00FFFFFF))
   def rgb(rgb:Int):Color = new Color(0xFF000000 | (rgb & 0x00FFFFFF))
   def rgba(rgba:Int):Color = rgb( (0xFFFFFF00 & rgba) >> 8 , 0x000000FF & rgba)
   def argb(argb:Int):Color = new Color(argb)

   def mix(colors:Iterable[Color]):Color = {
     val N = colors.size
     if(N==0) Black //the void
     else Color.rgb(
       colors.map{_.r}.sum/N,
       colors.map{_.g}.sum/N,
       colors.map{_.b}.sum/N,
       colors.map{_.a}.sum/N
     )
   }

   //colorbrewer2.org "9-class Set1"
   val Red             = Color(0xffe41a1c)
   val Blue            = Color(0xff377eb8)
   val Green           = Color(0xff4daf4a)
   val Purple          = Color(0xff984ea3)
   val Orange          = Color(0xffff7f00)
   val Yellow          = Color(0xffffff33)
   val Brown           = Color(0xffa65628)
   val Pink            = Color(0xfff781bf)
   val Grey            = Color(0xff999999)

   val Black           = Color(0xff000000)
   val Charcoal        = Color(0xff333333)
   val White           = Color(0xffFFFFFF)

   val Cyan            = Color(0xff00ffff)
   val Magenta         = Color(0xffff00ff)

   val Transparent     = Color(0x00000000) //transparent black

   lazy val drxColorNames = Map[String,Color](  //pluss drx color names
      "Red"                     -> Red,
      "Blue"                    -> Blue,
      "Green"                   -> Green,
      "Purple"                  -> Purple,
      "Orange"                  -> Orange,
      "Yellow"                  -> Yellow,
      "Brown"                   -> Brown,
      "Pink"                    -> Pink,
      "Grey"                    -> Grey,
      "Charcoal"                -> Charcoal,
      "Cyan"                    -> Cyan,
      "Magenta"                 -> Magenta
   )
   lazy val catColorNames = Map[String,Color](  //pluss drx color names
      "0"                       -> cat10(0),
      "1"                       -> cat10(1),
      "2"                       -> cat10(2),
      "3"                       -> cat10(3),
      "4"                       -> cat10(4),
      "5"                       -> cat10(5),
      "6"                       -> cat10(6),
      "7"                       -> cat10(7),
      "8"                       -> cat10(8),
      "9"                       -> cat10(9)
   )
   //--- from http://dev.w3.org/csswg/css-color-4/#named-colors
   lazy val cssColorNames = Map[String,Color](
      "aliceblue"               -> Color(0xfff0f8ff),
      "antiquewhite"            -> Color(0xfffaebd7),
      "aqua"                    -> Color(0xff00ffff),
      "aquamarine"              -> Color(0xff7fffd4),
      "azure"                   -> Color(0xfff0ffff),
      "beige"                   -> Color(0xfff5f5dc),
      "bisque"                  -> Color(0xffffe4c4),
      "black"                   -> Color(0xff000000),
      "blanchedalmond"          -> Color(0xffffebcd),
      "blue"                    -> Color(0xff0000ff),
      "blueviolet"              -> Color(0xff8a2be2),
      "brown"                   -> Color(0xffa52a2a),
      "burlywood"               -> Color(0xffdeb887),
      "cadetblue"               -> Color(0xff5f9ea0),
      "chartreuse"              -> Color(0xff7fff00),
      "chocolate"               -> Color(0xffd2691e),
      "coral"                   -> Color(0xffff7f50),
      "cornflowerblue"          -> Color(0xff6495ed),
      "cornsilk"                -> Color(0xfffff8dc),
      "crimson"                 -> Color(0xffdc143c),
      "cyan"                    -> Color(0xff00ffff),
      "darkblue"                -> Color(0xff00008b),
      "darkcyan"                -> Color(0xff008b8b),
      "darkgoldenrod"           -> Color(0xffb8860b),
      "darkgray"                -> Color(0xffa9a9a9),
      "darkgreen"               -> Color(0xff006400),
      "darkgrey"                -> Color(0xffa9a9a9),
      "darkkhaki"               -> Color(0xffbdb76b),
      "darkmagenta"             -> Color(0xff8b008b),
      "darkolivegreen"          -> Color(0xff556b2f),
      "darkorange"              -> Color(0xffff8c00),
      "darkorchid"              -> Color(0xff9932cc),
      "darkred"                 -> Color(0xff8b0000),
      "darksalmon"              -> Color(0xffe9967a),
      "darkseagreen"            -> Color(0xff8fbc8f),
      "darkslateblue"           -> Color(0xff483d8b),
      "darkslategray"           -> Color(0xff2f4f4f),
      "darkslategrey"           -> Color(0xff2f4f4f),
      "darkturquoise"           -> Color(0xff00ced1),
      "darkviolet"              -> Color(0xff9400d3),
      "deeppink"                -> Color(0xffff1493),
      "deepskyblue"             -> Color(0xff00bfff),
      "dimgray"                 -> Color(0xff696969),
      "dimgrey"                 -> Color(0xff696969),
      "dodgerblue"              -> Color(0xff1e90ff),
      "firebrick"               -> Color(0xffb22222),
      "floralwhite"             -> Color(0xfffffaf0),
      "forestgreen"             -> Color(0xff228b22),
      "fuchsia"                 -> Color(0xffff00ff),
      "gainsboro"               -> Color(0xffdcdcdc),
      "ghostwhite"              -> Color(0xfff8f8ff),
      "gold"                    -> Color(0xffffd700),
      "goldenrod"               -> Color(0xffdaa520),
      "gray"                    -> Color(0xff808080),
      "green"                   -> Color(0xff008000),
      "greenyellow"             -> Color(0xffadff2f),
      "grey"                    -> Color(0xff808080),
      "honeydew"                -> Color(0xfff0fff0),
      "hotpink"                 -> Color(0xffff69b4),
      "indianred"               -> Color(0xffcd5c5c),
      "indigo"                  -> Color(0xff4b0082),
      "ivory"                   -> Color(0xfffffff0),
      "khaki"                   -> Color(0xfff0e68c),
      "lavender"                -> Color(0xffe6e6fa),
      "lavenderblush"           -> Color(0xfffff0f5),
      "lawngreen"               -> Color(0xff7cfc00),
      "lemonchiffon"            -> Color(0xfffffacd),
      "lightblue"               -> Color(0xffadd8e6),
      "lightcoral"              -> Color(0xfff08080),
      "lightcyan"               -> Color(0xffe0ffff),
      "lightgoldenrodyellow"    -> Color(0xfffafad2),
      "lightgray"               -> Color(0xffd3d3d3),
      "lightgreen"              -> Color(0xff90ee90),
      "lightgrey"               -> Color(0xffd3d3d3),
      "lightpink"               -> Color(0xffffb6c1),
      "lightsalmon"             -> Color(0xffffa07a),
      "lightseagreen"           -> Color(0xff20b2aa),
      "lightskyblue"            -> Color(0xff87cefa),
      "lightslategray"          -> Color(0xff778899),
      "lightslategrey"          -> Color(0xff778899),
      "lightsteelblue"          -> Color(0xffb0c4de),
      "lightyellow"             -> Color(0xffffffe0),
      "lime"                    -> Color(0xff00ff00),
      "limegreen"               -> Color(0xff32cd32),
      "linen"                   -> Color(0xfffaf0e6),
      "magenta"                 -> Color(0xffff00ff),
      "maroon"                  -> Color(0xff800000),
      "mediumaquamarine"        -> Color(0xff66cdaa),
      "mediumblue"              -> Color(0xff0000cd),
      "mediumorchid"            -> Color(0xffba55d3),
      "mediumpurple"            -> Color(0xff9370db),
      "mediumseagreen"          -> Color(0xff3cb371),
      "mediumslateblue"         -> Color(0xff7b68ee),
      "mediumspringgreen"       -> Color(0xff00fa9a),
      "mediumturquoise"         -> Color(0xff48d1cc),
      "mediumvioletred"         -> Color(0xffc71585),
      "midnightblue"            -> Color(0xff191970),
      "mintcream"               -> Color(0xfff5fffa),
      "mistyrose"               -> Color(0xffffe4e1),
      "moccasin"                -> Color(0xffffe4b5),
      "navajowhite"             -> Color(0xffffdead),
      "navy"                    -> Color(0xff000080),
      "oldlace"                 -> Color(0xfffdf5e6),
      "olive"                   -> Color(0xff808000),
      "olivedrab"               -> Color(0xff6b8e23),
      "orange"                  -> Color(0xffffa500),
      "orangered"               -> Color(0xffff4500),
      "orchid"                  -> Color(0xffda70d6),
      "palegoldenrod"           -> Color(0xffeee8aa),
      "palegreen"               -> Color(0xff98fb98),
      "paleturquoise"           -> Color(0xffafeeee),
      "palevioletred"           -> Color(0xffdb7093),
      "papayawhip"              -> Color(0xffffefd5),
      "peachpuff"               -> Color(0xffffdab9),
      "peru"                    -> Color(0xffcd853f),
      "pink"                    -> Color(0xffffc0cb),
      "plum"                    -> Color(0xffdda0dd),
      "powderblue"              -> Color(0xffb0e0e6),
      "purple"                  -> Color(0xff800080),
      "rebeccapurple"           -> Color(0xff663399),
      "red"                     -> Color(0xffff0000),
      "rosybrown"               -> Color(0xffbc8f8f),
      "royalblue"               -> Color(0xff4169e1),
      "saddlebrown"             -> Color(0xff8b4513),
      "salmon"                  -> Color(0xfffa8072),
      "sandybrown"              -> Color(0xfff4a460),
      "seagreen"                -> Color(0xff2e8b57),
      "seashell"                -> Color(0xfffff5ee),
      "sienna"                  -> Color(0xffa0522d),
      "silver"                  -> Color(0xffc0c0c0),
      "skyblue"                 -> Color(0xff87ceeb),
      "slateblue"               -> Color(0xff6a5acd),
      "slategray"               -> Color(0xff708090),
      "slategrey"               -> Color(0xff708090),
      "snow"                    -> Color(0xfffffafa),
      "springgreen"             -> Color(0xff00ff7f),
      "steelblue"               -> Color(0xff4682b4),
      "tan"                     -> Color(0xffd2b48c),
      "teal"                    -> Color(0xff008080),
      "thistle"                 -> Color(0xffd8bfd8),
      "tomato"                  -> Color(0xffff6347),
      "turquoise"               -> Color(0xff40e0d0),
      "violet"                  -> Color(0xffee82ee),
      "wheat"                   -> Color(0xfff5deb3),
      "white"                   -> Color(0xffffffff),
      "whitesmoke"              -> Color(0xfff5f5f5),
      "yellow"                  -> Color(0xffffff00),
      "yellowgreen"             -> Color(0xff9acd32)
   )

   lazy val q1c9 = Vector(Red,Blue,Green,Purple,Orange,Yellow,Brown,Pink,Grey)
   lazy val q1c8 = Vector(Red,Blue,Green,Purple,Orange,Brown,Pink,Grey)
   lazy val q1c24 = q1c8 ++ q1c8.map(_.darker) ++ q1c8.map(_.darker.darker)

   // private def colorList(hexList:String):Vector[Color] = hexList.trim.split("""\s+""").toVector.map{hex => Color((hex base 16).toInt) alpha 0xff}
   private def colorList(hexList:String):Vector[Color] = hexList.filter(!_.isWhitespace).grouped(6).map{hex => Color((hex base 16).toInt) alpha 0xff}.toVector

   lazy val cat10 = colorList("""
      1f77b4
      ff7f0e
      2ca02c
      d62728
      9467bd
      8c564b
      e377c2
      7f7f7f
      bcbd22
      17becf
   """)
   lazy val cat20 = colorList("""
      1f77b4
      aec7e8
      ff7f0e
      ffbb78
      2ca02c
      98df8a
      d62728
      ff9896
      9467bd
      c5b0d5
      8c564b
      c49c94
      e377c2
      f7b6d2
      7f7f7f
      c7c7c7
      bcbd22
      dbdb8d
      17becf
      9edae5
   """)
   lazy val cat20b = colorList("""
      393b79
      5254a3
      6b6ecf
      9c9ede
      637939
      8ca252
      b5cf6b
      cedb9c
      8c6d31
      bd9e39
      e7ba52
      e7cb94
      843c39
      ad494a
      d6616b
      e7969c
      7b4173
      a55194
      ce6dbd
      de9ed6
   """)
   lazy val cat20c = colorList("""
      3182bd
      6baed6
      9ecae1
      c6dbef
      e6550d
      fd8d3c
      fdae6b
      fdd0a2
      31a354
      74c476
      a1d99b
      c7e9c0
      756bb1
      9e9ac8
      bcbddc
      dadaeb
      636363
      969696
      bdbdbd
      d9d9d9
   """)

   /** "perceptually uniform" colormaps
    *  https://github.com/d3/d3-scale
    *  https://raw.githubusercontent.com/d3/d3-scale/master/src/viridis.js
    *  https://bids.github.io/colormap/
    *  https://github.com/BIDS/colormap/blob/master/colormaps.py
    *  */
   // type ColorMap = Vector[Color]
   lazy val maps = Map( "viridis" -> viridis, "magma" -> magma, "plasma" -> plasma, "inferno" -> inferno)
   lazy val viridis  = colorList("44015444025645045745055946075a46085c460a5d460b5e470d60470e6147106347116447136548146748166848176948186a481a6c481b6d481c6e481d6f481f70482071482173482374482475482576482677482878482979472a7a472c7a472d7b472e7c472f7d46307e46327e46337f463480453581453781453882443983443a83443b84433d84433e85423f854240864241864142874144874045884046883f47883f48893e49893e4a893e4c8a3d4d8a3d4e8a3c4f8a3c508b3b518b3b528b3a538b3a548c39558c39568c38588c38598c375a8c375b8d365c8d365d8d355e8d355f8d34608d34618d33628d33638d32648e32658e31668e31678e31688e30698e306a8e2f6b8e2f6c8e2e6d8e2e6e8e2e6f8e2d708e2d718e2c718e2c728e2c738e2b748e2b758e2a768e2a778e2a788e29798e297a8e297b8e287c8e287d8e277e8e277f8e27808e26818e26828e26828e25838e25848e25858e24868e24878e23888e23898e238a8d228b8d228c8d228d8d218e8d218f8d21908d21918c20928c20928c20938c1f948c1f958b1f968b1f978b1f988b1f998a1f9a8a1e9b8a1e9c891e9d891f9e891f9f881fa0881fa1881fa1871fa28720a38620a48621a58521a68522a78522a88423a98324aa8325ab8225ac8226ad8127ad8128ae8029af7f2ab07f2cb17e2db27d2eb37c2fb47c31b57b32b67a34b67935b77937b87838b9773aba763bbb753dbc743fbc7340bd7242be7144bf7046c06f48c16e4ac16d4cc26c4ec36b50c46a52c56954c56856c66758c7655ac8645cc8635ec96260ca6063cb5f65cb5e67cc5c69cd5b6ccd5a6ece5870cf5773d05675d05477d1537ad1517cd2507fd34e81d34d84d44b86d54989d5488bd6468ed64590d74393d74195d84098d83e9bd93c9dd93ba0da39a2da37a5db36a8db34aadc32addc30b0dd2fb2dd2db5de2bb8de29bade28bddf26c0df25c2df23c5e021c8e020cae11fcde11dd0e11cd2e21bd5e21ad8e219dae319dde318dfe318e2e418e5e419e7e419eae51aece51befe51cf1e51df4e61ef6e620f8e621fbe723fde725")
   lazy val magma = colorList("00000401000501010601010802010902020b02020d03030f03031204041405041606051806051a07061c08071e0907200a08220b09240c09260d0a290e0b2b100b2d110c2f120d31130d34140e36150e38160f3b180f3d19103f1a10421c10441d11471e114920114b21114e22115024125325125527125829115a2a115c2c115f2d11612f116331116533106734106936106b38106c390f6e3b0f703d0f713f0f72400f74420f75440f764510774710784910784a10794c117a4e117b4f127b51127c52137c54137d56147d57157e59157e5a167e5c167f5d177f5f187f601880621980641a80651a80671b80681c816a1c816b1d816d1d816e1e81701f81721f817320817521817621817822817922827b23827c23827e24828025828125818326818426818627818827818928818b29818c29818e2a81902a81912b81932b80942c80962c80982d80992d809b2e7f9c2e7f9e2f7fa02f7fa1307ea3307ea5317ea6317da8327daa337dab337cad347cae347bb0357bb2357bb3367ab5367ab73779b83779ba3878bc3978bd3977bf3a77c03a76c23b75c43c75c53c74c73d73c83e73ca3e72cc3f71cd4071cf4070d0416fd2426fd3436ed5446dd6456cd8456cd9466bdb476adc4869de4968df4a68e04c67e24d66e34e65e44f64e55064e75263e85362e95462ea5661eb5760ec5860ed5a5fee5b5eef5d5ef05f5ef1605df2625df2645cf3655cf4675cf4695cf56b5cf66c5cf66e5cf7705cf7725cf8745cf8765cf9785df9795df97b5dfa7d5efa7f5efa815ffb835ffb8560fb8761fc8961fc8a62fc8c63fc8e64fc9065fd9266fd9467fd9668fd9869fd9a6afd9b6bfe9d6cfe9f6dfea16efea36ffea571fea772fea973feaa74feac76feae77feb078feb27afeb47bfeb67cfeb77efeb97ffebb81febd82febf84fec185fec287fec488fec68afec88cfeca8dfecc8ffecd90fecf92fed194fed395fed597fed799fed89afdda9cfddc9efddea0fde0a1fde2a3fde3a5fde5a7fde7a9fde9aafdebacfcecaefceeb0fcf0b2fcf2b4fcf4b6fcf6b8fcf7b9fcf9bbfcfbbdfcfdbf")
   lazy val inferno = colorList("00000401000501010601010802010a02020c02020e03021004031204031405041706041907051b08051d09061f0a07220b07240c08260d08290e092b10092d110a30120a32140b34150b37160b39180c3c190c3e1b0c411c0c431e0c451f0c48210c4a230c4c240c4f260c51280b53290b552b0b572d0b592f0a5b310a5c320a5e340a5f3609613809623909633b09643d09653e0966400a67420a68440a68450a69470b6a490b6a4a0c6b4c0c6b4d0d6c4f0d6c510e6c520e6d540f6d550f6d57106e59106e5a116e5c126e5d126e5f136e61136e62146e64156e65156e67166e69166e6a176e6c186e6d186e6f196e71196e721a6e741a6e751b6e771c6d781c6d7a1d6d7c1d6d7d1e6d7f1e6c801f6c82206c84206b85216b87216b88226a8a226a8c23698d23698f24699025689225689326679526679727669827669a28659b29649d29649f2a63a02a63a22b62a32c61a52c60a62d60a82e5fa92e5eab2f5ead305dae305cb0315bb1325ab3325ab43359b63458b73557b93556ba3655bc3754bd3853bf3952c03a51c13a50c33b4fc43c4ec63d4dc73e4cc83f4bca404acb4149cc4248ce4347cf4446d04545d24644d34743d44842d54a41d74b3fd84c3ed94d3dda4e3cdb503bdd513ade5238df5337e05536e15635e25734e35933e45a31e55c30e65d2fe75e2ee8602de9612bea632aeb6429eb6628ec6726ed6925ee6a24ef6c23ef6e21f06f20f1711ff1731df2741cf3761bf37819f47918f57b17f57d15f67e14f68013f78212f78410f8850ff8870ef8890cf98b0bf98c0af98e09fa9008fa9207fa9407fb9606fb9706fb9906fb9b06fb9d07fc9f07fca108fca309fca50afca60cfca80dfcaa0ffcac11fcae12fcb014fcb216fcb418fbb61afbb81dfbba1ffbbc21fbbe23fac026fac228fac42afac62df9c72ff9c932f9cb35f8cd37f8cf3af7d13df7d340f6d543f6d746f5d949f5db4cf4dd4ff4df53f4e156f3e35af3e55df2e661f2e865f2ea69f1ec6df1ed71f1ef75f1f179f2f27df2f482f3f586f3f68af4f88ef5f992f6fa96f8fb9af9fc9dfafda1fcffa4")
   lazy val plasma = colorList("0d088710078813078916078a19068c1b068d1d068e20068f2206902406912605912805922a05932c05942e05952f059631059733059735049837049938049a3a049a3c049b3e049c3f049c41049d43039e44039e46039f48039f4903a04b03a14c02a14e02a25002a25102a35302a35502a45601a45801a45901a55b01a55c01a65e01a66001a66100a76300a76400a76600a76700a86900a86a00a86c00a86e00a86f00a87100a87201a87401a87501a87701a87801a87a02a87b02a87d03a87e03a88004a88104a78305a78405a78606a68707a68808a68a09a58b0aa58d0ba58e0ca48f0da4910ea3920fa39410a29511a19613a19814a099159f9a169f9c179e9d189d9e199da01a9ca11b9ba21d9aa31e9aa51f99a62098a72197a82296aa2395ab2494ac2694ad2793ae2892b02991b12a90b22b8fb32c8eb42e8db52f8cb6308bb7318ab83289ba3388bb3488bc3587bd3786be3885bf3984c03a83c13b82c23c81c33d80c43e7fc5407ec6417dc7427cc8437bc9447aca457acb4679cc4778cc4977cd4a76ce4b75cf4c74d04d73d14e72d24f71d35171d45270d5536fd5546ed6556dd7566cd8576bd9586ada5a6ada5b69db5c68dc5d67dd5e66de5f65de6164df6263e06363e16462e26561e26660e3685fe4695ee56a5de56b5de66c5ce76e5be76f5ae87059e97158e97257ea7457eb7556eb7655ec7754ed7953ed7a52ee7b51ef7c51ef7e50f07f4ff0804ef1814df1834cf2844bf3854bf3874af48849f48948f58b47f58c46f68d45f68f44f79044f79143f79342f89441f89540f9973ff9983ef99a3efa9b3dfa9c3cfa9e3bfb9f3afba139fba238fca338fca537fca636fca835fca934fdab33fdac33fdae32fdaf31fdb130fdb22ffdb42ffdb52efeb72dfeb82cfeba2cfebb2bfebd2afebe2afec029fdc229fdc328fdc527fdc627fdc827fdca26fdcb26fccd25fcce25fcd025fcd225fbd324fbd524fbd724fad824fada24f9dc24f9dd25f8df25f8e125f7e225f7e425f6e626f6e826f5e926f5eb27f4ed27f3ee27f3f027f2f227f1f426f1f525f0f724f0f921")

   /**ansi color map
   * https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
   * code  i   bgr  color
   * ----------------
   *  3    0   000  black
   *  3    1   001  red
   *  3    2   010  green
   *  3    3   011  yellow
   *  3    4   100  blue
   *  3    5   101  purple(magenta)
   *  3    6   110  aqua (cyan)
   *  3    7   111  white
   */
   private def permute8(on:Int, off:Int)(key:Int => String) = (0 to 7).map{i =>
     def f(b:Int) = if(i bit b) on else off
     Color.rgb( f(0), f(1), f(2) ) -> key(i)
   }.toMap
   lazy val ansi8:Map[Color,String] = permute8(170,0){i=>s"${i}"}
   lazy val ansi16:Map[Color,String] = ansi8 ++ permute8(255,85){i=>s"${i};1"}
   /* TODO add ansi-256 color mode from https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
     lazy val ansi256:Map[Color,String] = ansi8 ++ permute8(255,85){i=>s"${i};1"}
     In 256-color mode (ESC[38;5;<fgcode>m and ESC[48;5;<bgcode>m), the color-codes are the following:[citation needed]

     0x00-0x07:  standard colors (as in ESC [ 30–37 m)
     0x08-0x0F:  high intensity colors (as in ESC [ 90–97 m)
     0x10-0xE7:  6 × 6 × 6 = 216 colors: 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5)
     0xE8-0xFF:  grayscale from black to white in 24 steps
  */

   2017-07-30("use the more general and clear cat20 % i instead","v0.1.20")
   def catColor(i:Int) = q1c24(i % q1c24.size)

   object HCL{
      // def hcl(H:Double,C:Double,L:Double,a:Int=0xff):HCL = HCL(Vec(H.rad)*C tz L, a)
      def hsl(H:Angle,S:Double,L:Double,a:Int=0xff):HCL = {
         val C = if(L<=0.5) S*2*L else S*2*(1-L)
         HCL(H,C,L,a)
      }
      def apply(H:Angle, C:Double, L:Double,a:Int):HCL = HCL(Vec.polar(C,H) tz L, a)
      def apply(H:Angle, C:Double, L:Double):HCL = HCL(Vec.polar(C,H) tz L, 0xff)
      def apply(color:Color):HCL ={
         //variables match en.wikipedia.org/wiki/HSL_and_HSV
         val R = color.r/255.0
         val G = color.g/255.0
         val B = color.b/255.0

         val M = (R max G) max B  //strongest color value
         val m = (R min G) min B  //weakest color
         val C = M-m  //chroma (relative size of the "chromacity plane" of the cube projeted on a hexagon)

         //warp the hexagon of rgb&cym to a circle
         val H = ((if (C == 0.0) 0.0  //rather undefined (it could really be any hue)
                 else if(C == R)  ((G-B)/C) % 6.0
                 else if(C == G)  ((B-R)/C) + 2.0
                 else             ((R-G)/C) + 4.0
               )*60.0).deg //convert to degrees for hue



         val L = (M+m)/2                  //Lightness: average of largest and smallet color components
         /*
         val Y = 0.3*R + 0.59*G + 0.11*B  //Luma: gamma corrected RGB for percieved luminance
         val V = M                        //Value: largest component of color
         val S_hsv = if(V==0.0) 0.0 else C/V    //Value adjusted/validated to 0.0 to 1.0
         val S_hsl = if(L==0.0 || L == 1.0) 0.0 else C/(1 - (2*L-1).abs)  //Lightness adjusted/validated on 0.0 to 1.0 scale
         */

         HCL(Vec.polar(C,H) tz L, color.a)
      }
   }
   case class HCL(vec:Vec,a:Int=0){
      lazy val H:Angle = vec.heading //heading as a positive number in deg
      lazy val C:Double = vec.range //0 to 1 ??
      lazy val L:Double = vec.z     //0 to 1 ??

      lazy val rgb:Color = {
         val Hp = H.norm.deg/60.0
         val X = C*(1 - (Hp % 2 - 1).abs)

         val (r,g,b) = if     (Hp < 1) (C,     X,  0.0)
                       else if(Hp < 2) (X,     C,  0.0)
                       else if(Hp < 3) (0.0,   C,    X)
                       else if(Hp < 4) (0.0,   X,    C)
                       else if(Hp < 5) (X,   0.0,    C)
                       else            (C,   0.0,    X)
         val m = L - C/2
         Color.rgb(
            ((r+m)*255).round.toInt,
            ((g+m)*255).round.toInt,
            ((b+m)*255).round.toInt,
            a
         )
      }

      def lerp(that:HCL)(amt:Double):HCL =  HCL(
         (this.vec lerp that.vec)(amt),  //  hcl vec
         (this.a   lerp that.a  )(amt)   //alpha channel
      )
   }
   /*
   case class CYMK(c:Int,y:Int,m:Int,k:Int){
      lazy val rbg:Color =  ???
   }
   */
}
trait Colorizer[A,B]{
  def apply(c:Color, a:A):B
}
object Colorizer{
  implicit object ColorizerAnsi extends Colorizer[String,String]{
    def apply(c:Color, a:String):String = c.ansi(a)
  }
}
//TODO make color a trait that RGB and HCL and others extend, however this may have large impacts
class Color(val argb:Int) extends AnyVal{
   def apply[A,B](a:A)(implicit c:Colorizer[A,B]):B = c(this,a)

   def toAwt:java.awt.Color =  new java.awt.Color(r, g, b, a)
   def cssHex:String = "#" + hex.takeRight(6)
   def hex = f"0x$argb%08X"
   override def toString = s"Color($hex)"

   def alpha(a:Int):Color = Color(a<<24 | (0x00ffffff & argb) )
   def opacity(ratio:Double):Color = alpha((0xFF * ratio).toInt.sat(0,0xFF))

   def a:Int = argb >> 24 & 0xFF
   def r:Int = argb >> 16 & 0xFF
   def g:Int = argb >> 8  & 0xFF
   def b:Int = argb       & 0xFF

   def ~(that:Color):Bound[Color] = Bound(this,that)

   def invert:Color = Color.rgb(255-r, 255-g, 255-b, a)
   def complement:Color = {
      val rgb = Seq(r,g,b)
      val m = rgb.min + rgb.max
      Color.rgb(m-r, m-g, m-b,a)
   }

   /**rgb as a drx.Vec*/
   def vec:Vec = Vec(r,g,b)

   /**relative distance metric used for closeness https://en.wikipedia.org/wiki/Color_difference */
   def dist(that:Color):Double = {
     // val weight = Vec(1,1,1) //euclidean rgb
     // val weight = Vec(2,4,3) //rgb perception weighted euclidan
     val weight = {
       val r = (this.r + that.r)/2d
       def q(n:Double) = 2d + n/256d
       Vec(q(r),4,q(255-r)) //red blue scaling
     }
     val D = this.vec - that.vec
     math.sqrt(D.sq * weight)
   }
   def closest(colors:Iterable[Color]):Color = colors minBy dist
   def cssName:String = closestName(Color.cssColorNames)
   def drxName:String = closestName(Color.drxColorNames)
   def closestName(nameMap:Map[String,Color]):String = nameMap.minBy{case (_,c) => dist(c)}._1 // FIXME TODO use cssName table

   //https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
   private def ansiCode:String = Color.ansi16(closest(Color.ansi16.keys))
   private def ansiWrap(code:String)(body:String) = s"\u001b[${code}m${body}\u001b[0m"
   def ansi(body:String):String = ansiWrap(s"3${ansiCode}")(body)
   def ansi(body:String,bg:Color):String = ansiWrap(s"4${bg.ansiCode};3${ansiCode}")(body) //3:forground 4:background

   //TODO move this to a generic logarithmic scaling function in bounds or scales
   private def adjust(k:Double, min:Int=0, max:Int=0xFF)(x:Int):Int = ((0.7 ** k)*x).round.toInt.sat(min,max)
   def map(f:Int => Int):Color =  Color.rgb( f(r), f(g), f(b), a)

   def brighter(k:Double):Color = this map adjust(-k, 30, 0xFF)
   def darker(k:Double):Color   = this map adjust( k,  0, 0xFF)
   def brighter:Color = brighter(1.0)
   def darker:Color   = darker(1.0)

   def isFullyTransparent:Boolean = a == 0x00
   def isTransparent:Boolean = a != 0xFF //warning: this method name can be misleading is it completely transparent or partially?
   def isOpaque:Boolean = a == 0xFF //opaque by definition is fullyOpaque or not even partially transparent
   def moreTransparent:Color = moreTransparent(1.0)
   def moreTransparent(k:Double=1.0):Color = alpha(adjust(k, 0, 0xFF)(a))
   def moreOpaque:Color = moreOpaque(1.0)
   def moreOpaque(k:Double=1.0):Color = alpha(adjust(-k, 0, 0xFF)(a))

   2017-07-30("use d3js like darker instead","v0.1.3")
   def darken(v:Int):Color = this map {c => (c - v).sat(0,0xFF)}

   def lerp(that:Color)(amt:Double):Color = Color.rgb(
      (this.r lerp that.r)(amt),
      (this.g lerp that.g)(amt),
      (this.b lerp that.b)(amt),
      (this.a lerp that.a)(amt)
   )

   def hcl = Color.HCL(this)
   //private def lerpHSL(that:Color,amt:Double):Color = ???

   //--convenience functions to construct styles
   def fill = Style.Fill(this)
   def stroke = Style.Stroke(this)
}