import '../../image.dart';
import '../../image_exception.dart';
import '../../util/input_buffer.dart';
import 'psd_blending_ranges.dart';
import 'psd_channel.dart';
import 'psd_image.dart';
import 'psd_layer_data.dart';
import 'psd_mask.dart';
import 'effect/psd_bevel_effect.dart';
import 'effect/psd_drop_shadow_effect.dart';
import 'effect/psd_effect.dart';
import 'effect/psd_inner_glow_effect.dart';
import 'effect/psd_inner_shadow_effect.dart';
import 'effect/psd_outer_glow_effect.dart';
import 'effect/psd_solid_fill_effect.dart';
import 'layer_data/psd_layer_additional_data.dart';
import 'layer_data/psd_layer_section_divider.dart';

class PsdLayer {
  int top;
  int left;
  int bottom;
  int right;
  int width;
  int height;
  int blendMode;
  int opacity;
  int clipping;
  int flags;
  int compression;
  String name;
  List<PsdChannel> channels;
  PsdMask mask;
  PsdBlendingRanges blendingRanges;
  Map<String, PsdLayerData> additionalData = {};
  List<PsdLayer> children = [];
  PsdLayer parent;
  Image layerImage;
  List<PsdEffect> effects = [];

  static const int SIGNATURE = 0x3842494d; // '8BIM'

  static const int BLEND_PASSTHROUGH = 0x70617373; // 'pass'
  static const int BLEND_NORMAL = 0x6e6f726d; // 'norm'
  static const int BLEND_DISSOLVE = 0x64697373; // 'diss'
  static const int BLEND_DARKEN = 0x6461726b; // 'dark'
  static const int BLEND_MULTIPLY = 0x6d756c20; // 'mul '
  static const int BLEND_COLOR_BURN = 0x69646976; // 'idiv'
  static const int BLEND_LINEAR_BURN = 0x6c62726e; // 'lbrn'
  static const int BLEND_DARKEN_COLOR = 0x646b436c; // 'dkCl'
  static const int BLEND_LIGHTEN = 0x6c697465; // 'lite'
  static const int BLEND_SCREEN = 0x7363726e; // 'scrn'
  static const int BLEND_COLOR_DODGE = 0x64697620; // 'div '
  static const int BLEND_LINEAR_DODGE = 0x6c646467; // 'lddg'
  static const int BLEND_LIGHTER_COLOR = 0x6c67436c; // 'lgCl'
  static const int BLEND_OVERLAY = 0x6f766572; // 'over'
  static const int BLEND_SOFT_LIGHT = 0x734c6974; // 'sLit'
  static const int BLEND_HARD_LIGHT = 0x684c6974; // 'hLit'
  static const int BLEND_VIVID_LIGHT = 0x764c6974; // 'vLit'
  static const int BLEND_LINEAR_LIGHT = 0x6c4c6974; // lLit'
  static const int BLEND_PIN_LIGHT = 0x704c6974; // 'pLit'
  static const int BLEND_HARD_MIX = 0x684d6978; // 'hMix'
  static const int BLEND_DIFFERENCE = 0x64696666; // 'diff'
  static const int BLEND_EXCLUSION = 0x736d7564; // 'smud'
  static const int BLEND_SUBTRACT = 0x66737562; // 'fsub'
  static const int BLEND_DIVIDE = 0x66646976; // 'fdiv'
  static const int BLEND_HUE = 0x68756520; // 'hue '
  static const int BLEND_SATURATION = 0x73617420; // 'sat '
  static const int BLEND_COLOR = 0x636f6c72; // 'colr'
  static const int BLEND_LUMINOSITY = 0x6c756d20; // 'lum '

  static const int FLAG_TRANSPARENCY_PROTECTED = 1;
  static const int FLAG_HIDDEN = 2;
  static const int FLAG_OBSOLETE = 4;
  static const int FLAG_PHOTOSHOP_5 = 8;
  static const int FLAG_PIXEL_DATA_IRRELEVANT_TO_APPEARANCE = 16;


  PsdLayer([InputBuffer input]) {
    if (input == null) {
      return;
    }

    top = input.readInt32();
    left = input.readInt32();
    bottom = input.readInt32();
    right = input.readInt32();
    width = right - left;
    height = bottom - top;

    channels = [];
    int numChannels = input.readUint16();
    for (int i = 0; i < numChannels; ++i) {
      int id = input.readInt16();
      int len = input.readUint32();
      channels.add(new PsdChannel(id, len));
    }

    int sig = input.readUint32();
    if (sig != SIGNATURE) {
      throw new ImageException('Invalid PSD layer signature: '
                               '${sig.toRadixString(16)}');
    }

    blendMode = input.readUint32();
    opacity = input.readByte();
    clipping = input.readByte();
    flags = input.readByte();

    int filler = input.readByte(); // should be 0
    if (filler != 0) {
      throw new ImageException('Invalid PSD layer data');
    }

    int len = input.readUint32();
    InputBuffer extra = input.readBytes(len);

    if (len > 0) {
      // Mask Data
      len = extra.readUint32();
      assert(len == 0 || len == 20 || len == 36);
      if (len > 0) {
        InputBuffer maskData = extra.readBytes(len);
        mask = new PsdMask(maskData);
      }

      // Layer Blending Ranges
      len = extra.readUint32();
      if (len > 0) {
        InputBuffer data = extra.readBytes(len);
        blendingRanges = new PsdBlendingRanges(data);
      }

      // Layer name
      len = extra.readByte();
      name = extra.readString(len);
      // Layer name is padded to a multiple of 4 bytes.
      int padding = (4 - (len % 4)) - 1;
      if (padding > 0) {
        extra.skip(padding);
      }

      // Additional layer sections
      while (!extra.isEOS) {
        int sig = extra.readUint32();
        if (sig != SIGNATURE) {
          throw new ImageException('PSD invalid signature for layer additional '
                                   'data: ${sig.toRadixString(16)}');
        }

        String tag = extra.readString(4);

        len = extra.readUint32();
        InputBuffer data = extra.readBytes(len);
        // pad to an even byte count.
        if (len & 1 == 1) {
          extra.skip(1);
        }

        additionalData[tag] = new PsdLayerData(tag, data);

        // Layer effects data
        if (tag == 'lrFX') {
          var fxData = (additionalData['lrFX'] as PsdLayerAdditionalData);
          var data = new InputBuffer.from(fxData.data);
          int version = data.readUint16();
          int numFx = data.readUint16();

          for (int j = 0; j < numFx; ++j) {
            var tag = data.readString(4); // 8BIM
            var fxTag = data.readString(4);
            int size = data.readUint32();

            if (fxTag == 'dsdw') {
              var fx = new PsdDropShadowEffect();
              effects.add(fx);
              fx.version = data.readUint32();
              fx.blur = data.readUint32();
              fx.intensity = data.readUint32();
              fx.angle = data.readUint32();
              fx.distance = data.readUint32();
              fx.color = [data.readUint16(), data.readUint16(),
                          data.readUint16(), data.readUint16(),
                          data.readUint16()];
              fx.blendMode = data.readString(8);
              fx.enabled = data.readByte() != 0;
              fx.globalAngle = data.readByte() != 0;
              fx.opacity = data.readByte();
              fx.nativeColor = [data.readUint16(), data.readUint16(),
                                data.readUint16(), data.readUint16(),
                                data.readUint16()];
            } else if (fxTag == 'isdw') {
              var fx = new PsdInnerShadowEffect();
              effects.add(fx);
              fx.version = data.readUint32();
              fx.blur = data.readUint32();
              fx.intensity = data.readUint32();
              fx.angle = data.readUint32();
              fx.distance = data.readUint32();
              fx.color = [data.readUint16(), data.readUint16(),
                          data.readUint16(), data.readUint16(),
                          data.readUint16()];
              fx.blendMode = data.readString(8);
              fx.enabled = data.readByte() != 0;
              fx.globalAngle = data.readByte() != 0;
              fx.opacity = data.readByte();
              fx.nativeColor = [data.readUint16(), data.readUint16(),
                                data.readUint16(), data.readUint16(),
                                data.readUint16()];
            } else if (fxTag == 'oglw') {
              var fx = new PsdOuterGlowEffect();
              effects.add(fx);
              fx.version = data.readUint32();
              fx.blur = data.readUint32();
              fx.intensity = data.readUint32();
              fx.color = [data.readUint16(), data.readUint16(),
                          data.readUint16(), data.readUint16(),
                          data.readUint16()];
              fx.blendMode = data.readString(8);
              fx.enabled = data.readByte() != 0;
              fx.opacity = data.readByte();
              if (fx.version == 2) {
                fx.nativeColor = [data.readUint16(), data.readUint16(),
                                  data.readUint16(), data.readUint16(),
                                  data.readUint16()];
              }
            } else if (fxTag == 'iglw') {
              var fx = new PsdInnerGlowEffect();
              effects.add(fx);
              fx.version = data.readUint32();
              fx.blur = data.readUint32();
              fx.intensity = data.readUint32();
              fx.color = [data.readUint16(), data.readUint16(),
                          data.readUint16(), data.readUint16(),
                          data.readUint16()];
              fx.blendMode = data.readString(8);
              fx.enabled = data.readByte() != 0;
              fx.opacity = data.readByte();
              if (fx.version == 2) {
                fx.invert = data.readByte() != 0;
                fx.nativeColor = [data.readUint16(), data.readUint16(),
                                  data.readUint16(), data.readUint16(),
                                  data.readUint16()];
              }
            } else if (fxTag == 'bevl') {
              var fx = new PsdBevelEffect();
              effects.add(fx);
              fx.version = data.readUint32();
              fx.angle = data.readUint32();
              fx.strength = data.readUint32();
              fx.blur = data.readUint32();
              fx.highlightBlendMode = data.readString(8);
              fx.shadowBlendMode = data.readString(8);
              fx.highlightColor = [data.readUint16(), data.readUint16(),
                                   data.readUint16(), data.readUint16(),
                                   data.readUint16()];
              fx.shadowColor = [data.readUint16(), data.readUint16(),
                                data.readUint16(), data.readUint16(),
                                data.readUint16()];
              fx.bevelStyle = data.readByte();
              fx.highlightOpacity = data.readByte();
              fx.shadowOpacity = data.readByte();
              fx.enabled = data.readByte() != 0;
              fx.globalAngle = data.readByte() != 0;
              fx.upOrDown = data.readByte();
              if (fx.version == 2) {
                fx.realHighlightColor = [data.readUint16(), data.readUint16(),
                                         data.readUint16(), data.readUint16(),
                                         data.readUint16()];
                fx.realShadowColor = [data.readUint16(), data.readUint16(),
                                      data.readUint16(), data.readUint16(),
                                      data.readUint16()];
              }
            } else if (fxTag == 'sofi') {
              var fx = new PsdSolidFillEffect();
              effects.add(fx);
              fx.version = data.readUint32();
              fx.blendMode = data.readString(4);
              fx.color = [data.readUint16(), data.readUint16(),
                          data.readUint16(), data.readUint16(),
                          data.readUint16()];
              fx.opacity = data.readByte();
              fx.enabled = data.readByte() != 0;
              fx.nativeColor = [data.readUint16(), data.readUint16(),
                                data.readUint16(), data.readUint16(),
                                data.readUint16()];
            } else {
              data.skip(size);
            }
          }
        }
      }
    }
  }

  /**
   * Is this layer visible?
   */
  bool isVisible() => flags & FLAG_HIDDEN == 0;

  /**
   * Is this layer a folder?
   */
  int type() {
    if (additionalData.containsKey(PsdLayerSectionDivider.TAG)) {
      PsdLayerSectionDivider section = additionalData[PsdLayerSectionDivider.TAG];
      return section.type;
    }
    return PsdLayerSectionDivider.NORMAL;
  }

  /**
   * Get the channel for the given [id].
   * Returns null if the layer does not have the given channel.
   */
  PsdChannel getChannel(int id) {
    for (int i = 0; i < channels.length; ++i) {
      if (channels[i].id == id) {
        return channels[i];
      }
    }
    return null;
  }

  void readImageData(InputBuffer input, PsdImage psd) {
    for (int i = 0; i < channels.length; ++i) {
      channels[i].readPlane(input, width, height, psd.depth);
    }

    layerImage = PsdImage.createImageFromChannels(psd.colorMode, psd.depth,
                                                  width, height, channels);
  }
}
