import {
  BOTTOM, TOP, DIRECTION, SIDE_CLASS, POLY_CLASS,
  POSITION_ABSOLUTE, REVERSE, LEFT, RIGHT, SIDE_CSS
} from "./consts";

function splitUnit(text: string) {
  const matches = /^([^\d|e|\-|\+]*)((?:\d|\.|-|e-|e\+)+)(\S*)$/g.exec(text);

  if (!matches) {
    return { unit: "", value: NaN };
  }
  const value = matches[2];
  const unit = matches[3];

  return { unit, value: parseFloat(value) };
}
function getTransformOrigin(strokeWidth: string | number, isVertical: boolean) {
  const x = isVertical ? strokeWidth : "50%";
  const y = isVertical ? "50%" : strokeWidth;

  return `transform-origin:${x} ${y};`;
}

function getHTML(no: number | string, style: string, content: string = "") {
  return `<div class="${SIDE_CLASS}${no}" style="${style}">${content}</div>`;
}
function makeDOM(tag: string, className: string) {
  const el = document.createElement(tag);

  el.className = className;
  return el;
}
function getSin(side: number) {
  // sin([n / 2] * 180 / n)
  return Math.sin(Math.floor(side / 2) * Math.PI / side);
}
function getCos(side: number) {
  // cos(180 / n)
  return Math.cos(Math.PI / side);
}
function getStarAngle(side: number, radius: number) {
  const cos = getCos(side);

  return 100 * cos <= radius ? 0 :
    Math.atan((cos - radius / 100) / Math.sin(Math.PI / side)) / Math.PI * 180;
}
function getSideSize(side: number, split: number,
                     starAngle: number, strokeWidth: string | number) {
  const starRatio = starAngle ? 1 / Math.cos(starAngle * Math.PI / 180) : 1;
  let ratio = 1 / split;

  if (side > 4) {
    const sin = Math.sin(Math.PI / side);
    const cos = Math.cos(Math.PI / side);

    if (side % 4 === 0) {
      // 2 * r * cos(180 / n) = w
      // 2 * r * sin(180 / n) = size
      ratio *= sin / cos;
    } else if (side % 2 === 0) {
      ratio *= sin;
    } else {
      // 2 * r * sin([n /2] * 180 / n) = w
      // 2 * r * sin(180 / n) = size
      ratio *= sin / getSin(side);
    }
  } else if (split === 1) {
    return "100%";
  }
  ratio *= starRatio;

  return `calc(100% * ${ratio} - ${strokeWidth} * ${ratio} + ${strokeWidth})`;
}
function getHeight(side: number, strokeWidth: string | number, isVertical: boolean) {
  if (side % 4 === 0) {
    return "100%";
  }
  const cos = getCos(side);

  let ratio = 0;
  if (side % 2 === 0) {
    // w * cos = h (vertical)
    // w / cos = h (horizontal)
    ratio = isVertical ? cos : 1 / cos;
  } else {
    const sin = getSin(side);

    if (isVertical) {
      // 2 * r * sin([n /2] * 180 / n) = w
      ratio = 0.5 / sin * (1 + cos);
    } else {
      // r * (1 + cos(180 / n)) = w
      ratio = 2 * 1 / (1 + cos) * sin;
    }
  }
  return `calc(100% * ${ratio} + ${strokeWidth} * ${1 - ratio})`;
}

function getFirstTransform(side: number, split: number, isVertical: boolean,
                           starAngle: number, strokeValue: number, strokeUnit: string) {
  const translateProperty = isVertical ? "X" : "Y";
  const cos = Math.cos(starAngle * Math.PI / 180);

  const arr = [];
  arr.push(`translate${translateProperty}(-${50 * cos * split}%)`);
  if (split > 1) {
    arr.push(`translate${translateProperty}(${strokeValue / 2 * (cos * split - 1)}${strokeUnit})`);
  }
  if (starAngle !== 0) {
    arr.push(`rotate(${starAngle}deg)`);
  }
  return `transform:${arr.join(" ")};`;
}

function getAttribute(el: HTMLElement, name: string) {
  return el.getAttribute(`data-${name}`) || undefined;
}
export function dom(el: HTMLElement) {
  const strokeWidth = getAttribute(el, "stroke-width");
  const stroke = getAttribute(el, "stroke");
  const direction = getAttribute(el, "direction") as DIRECTION;
  let side: string | number = getAttribute(el, "side");
  let split: string | number = getAttribute(el, "split");
  let starRadius: string | number = getAttribute(el, "star-radius");

  side && (side = parseFloat(side));
  split && (split = parseFloat(split));
  starRadius && (starRadius = parseFloat(starRadius));

  return poly({
    side: (side as number),
    split: (split as number),
    starRadius: (starRadius as number),
    strokeWidth, direction, stroke, container: el});
}
interface PolyInterface {
  side?: number ;
  split?: number;
  strokeWidth: number | string;
  className?: string;
  starRadius?: number;
  container?: HTMLElement;
  direction?: DIRECTION;
  stroke?: string;
}
export function css({
  strokeWidth = 0,
  side = 3,
  split = 1,
  starRadius = 100,
  stroke = "black",
  direction = BOTTOM,
}: PolyInterface) {
  const splitCount = split * (starRadius === 100 ? 1 : 2);
  const { unit: strokeUnit, value: strokeValue } = splitUnit(`${strokeWidth}`);
  const half = `${strokeValue / 2}${strokeUnit}`;
  const reverseDirection = REVERSE[direction];
  const isVertical = direction === TOP || direction === BOTTOM;
  const directionProperty = isVertical ? LEFT : TOP;
  const otherDirectionProperty = isVertical ? TOP : LEFT;
  const padding = getHeight(side, strokeWidth, isVertical);
  const sign = direction === TOP || direction === RIGHT ? -1 : 1;
  const starAngle = starRadius === 100 ? 0 : getStarAngle(side, starRadius);
  const sideWidth = getSideSize(side, splitCount, starAngle, strokeWidth);
  const width = isVertical ? sideWidth : strokeWidth;
  const height = isVertical ? strokeWidth : sideWidth;
  const externalAngle = 360 / side + 2 * starAngle;
  const transformSplit = getFirstTransform(side, splitCount, isVertical, sign * starAngle, strokeValue, strokeUnit);
  const sides = [];

  sides.push(`${reverseDirection}:0;
  ${otherDirectionProperty}:auto;
  ${directionProperty}:50%;${transformSplit}${getTransformOrigin(half, isVertical)}
  width:${width};height:${height};border-radius:${half};background:${stroke};`);
  for (let i = 0; i < side; ++i) {
    for (let j = 0; j < splitCount; ++j) {
      const no = i * splitCount + j;

      if (no === 0) {
        continue;
      }
      const transform = j === 0 ? `transform:rotate(${sign * externalAngle}deg)` :
        (starAngle && j === splitCount / 2 ? `transform:rotate(${-sign * 2 * starAngle}deg)` : "transform:none;");

      sides.push(`${otherDirectionProperty}:auto;${directionProperty}: calc(100% - ${strokeWidth});${transform}`);
    }
  }
  return { sides, percent: `padding-top:${padding}` };
}
export function be(el: HTMLElement, {
  strokeWidth = 0,
  side = 3,
  starRadius = 100,
  stroke,
  direction = BOTTOM,
}: PolyInterface) {
  const sideElements = el.querySelectorAll(`.${SIDE_CLASS}`);
  const length = sideElements.length;
  const split = length / (side * (starRadius !== 100 ? 2 : 1));
  if (!length || split % 1) {
    return;
  }
  const style = getComputedStyle(sideElements[0]);
  const elStrokeWidth = strokeWidth || (direction === TOP || direction === BOTTOM) ? style.height : style.width;
  const elStroke = stroke || style.backgroundColor;
  const percentElement = el.querySelector(`.${SIDE_CLASS}-percent`);
  const {sides, percent} = css({strokeWidth: elStrokeWidth, side, split, starRadius, stroke: elStroke, direction});

  sides.forEach((sideCSS, i) => {
    (sideElements[i] as any).style.cssText += sideCSS;
  });
  (percentElement as any).style.cssText += percent;
}
export function poly({
  className = POLY_CLASS,
  strokeWidth = 0,
  side = 3,
  split = 1,
  starRadius = 100,
  stroke = "black",
  direction = BOTTOM,
  container = makeDOM("div", className),
}: PolyInterface) {
  const {sides, percent} = css({strokeWidth, side, split, starRadius, stroke, direction});
  let html;

  for (let i = sides.length - 1; i >= 1; --i) {
    html = getHTML(` ${SIDE_CLASS}${i}`, SIDE_CSS + sides[i], html);
  }
  html = getHTML(` ${SIDE_CLASS}0`, `${POSITION_ABSOLUTE}display:inline-block;${sides[0]}`, html);

  const percentHTML = getHTML("-percent", `position:relative;width:100%;${percent}`);

  const position = getComputedStyle(container).position;

  if (!position || position === "static") {
    container.style.position = "relative";
  }
  container.insertAdjacentHTML("beforeend", html + percentHTML);

  return container;
}

export const VERSION = "#__VERSION__#";
