/*
 * Copyright 2021 Juan Cruz Viotti
 *
 * 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.
 */

import {
  strict as assert
} from 'assert'

import {
  JSONValue
} from '../json'

import {
  BaseEncodingDefinition
} from './base-encoding-definition'

import {
  EncodingType
} from '../encoder'

import {
  UINT8_MAX
} from '../utils/limits'

import {
  StringEncodingSchema
} from '../schema'

import {
  ENGLISH_DICTIONARY
} from '../encoder/string/dictionaries'

import {
  NoOptions,
  BoundedOptions,
  RoofOptions,
  FloorOptions,
  DictionaryOptions,
  SizeOptions
} from '../encoder/string/options'

export interface STRING_BROTLI_ENCODING extends BaseEncodingDefinition {
  readonly type: EncodingType.String;
  readonly encoding: 'STRING_BROTLI';
  readonly options: NoOptions;
}

export interface STRING_DICTIONARY_COMPRESSOR_ENCODING extends BaseEncodingDefinition {
  readonly type: EncodingType.String;
  readonly encoding: 'STRING_DICTIONARY_COMPRESSOR';
  readonly options: DictionaryOptions;
}

export interface URL_PROTOCOL_HOST_REST_ENCODING extends BaseEncodingDefinition {
  readonly type: EncodingType.String;
  readonly encoding: 'URL_PROTOCOL_HOST_REST';
  readonly options: NoOptions;
}

export interface RFC3339_DATE_INTEGER_TRIPLET_ENCODING extends BaseEncodingDefinition {
  readonly type: EncodingType.String;
  readonly encoding: 'RFC3339_DATE_INTEGER_TRIPLET';
  readonly options: NoOptions;
}

export interface BOUNDED_PREFIX_LENGTH_8BIT_FIXED_ENCODING extends BaseEncodingDefinition {
  readonly type: EncodingType.String;
  readonly encoding: 'BOUNDED_PREFIX_LENGTH_8BIT_FIXED';
  readonly options: BoundedOptions;
}

export interface ROOF_PREFIX_LENGTH_ENUM_VARINT_ENCODING extends BaseEncodingDefinition {
  readonly type: EncodingType.String;
  readonly encoding: 'ROOF_PREFIX_LENGTH_ENUM_VARINT';
  readonly options: RoofOptions;
}

export interface FLOOR_PREFIX_LENGTH_ENUM_VARINT_ENCODING extends BaseEncodingDefinition {
  readonly type: EncodingType.String;
  readonly encoding: 'FLOOR_PREFIX_LENGTH_ENUM_VARINT';
  readonly options: FloorOptions;
}

export interface UTF8_STRING_NO_LENGTH_ENCODING extends BaseEncodingDefinition {
  readonly type: EncodingType.String;
  readonly encoding: 'UTF8_STRING_NO_LENGTH';
  readonly options: SizeOptions;
}

export interface STRING_UNBOUNDED_SCOPED_PREFIX_LENGTH_ENCODING extends BaseEncodingDefinition {
  readonly type: EncodingType.String;
  readonly encoding: 'STRING_UNBOUNDED_SCOPED_PREFIX_LENGTH';
  readonly options: NoOptions;
}

export type StringEncodingNames =
  'STRING_BROTLI' |
  'STRING_DICTIONARY_COMPRESSOR' |
  'URL_PROTOCOL_HOST_REST' |
  'RFC3339_DATE_INTEGER_TRIPLET' |
  'BOUNDED_PREFIX_LENGTH_8BIT_FIXED' |
  'ROOF_PREFIX_LENGTH_ENUM_VARINT' |
  'FLOOR_PREFIX_LENGTH_ENUM_VARINT' |
  'UTF8_STRING_NO_LENGTH' |
  'STRING_UNBOUNDED_SCOPED_PREFIX_LENGTH'
export type StringEncoding =
  STRING_BROTLI_ENCODING |
  STRING_DICTIONARY_COMPRESSOR_ENCODING |
  URL_PROTOCOL_HOST_REST_ENCODING |
  RFC3339_DATE_INTEGER_TRIPLET_ENCODING |
  BOUNDED_PREFIX_LENGTH_8BIT_FIXED_ENCODING |
  ROOF_PREFIX_LENGTH_ENUM_VARINT_ENCODING |
  FLOOR_PREFIX_LENGTH_ENUM_VARINT_ENCODING |
  UTF8_STRING_NO_LENGTH_ENCODING |
  STRING_UNBOUNDED_SCOPED_PREFIX_LENGTH_ENCODING

export const getStringStates = (_schema: StringEncodingSchema): number | JSONValue[] => {
  return Infinity
}

export const getStringEncoding = (schema: StringEncodingSchema, _level: number): StringEncoding => {
  if (schema.format === 'date') {
    return {
      type: EncodingType.String,
      encoding: 'RFC3339_DATE_INTEGER_TRIPLET',
      options: {}
    }
  } else if (schema.format === 'uri') {
    return {
      type: EncodingType.String,
      encoding: 'URL_PROTOCOL_HOST_REST',
      options: {}
    }
  }

  // Markdown content tends to be significantly large to justify
  // the use of a proper general lossless compressor
  if (schema.contentMediaType === 'text/markdown') {
    return {
      type: EncodingType.String,
      encoding: 'STRING_BROTLI',
      options: {}
    }
  }

  assert(typeof schema.minLength === 'undefined' || schema.minLength >= 0)
  assert(typeof schema.maxLength === 'undefined' || schema.maxLength >= 0)
  assert(typeof schema.minLength === 'undefined' ||
    typeof schema.maxLength === 'undefined' ||
    schema.maxLength >= schema.minLength)

  if ((typeof schema.minLength !== 'undefined' && schema.minLength > 0) && typeof schema.maxLength !== 'undefined') {
    if (schema.maxLength - schema.minLength <= UINT8_MAX - 1) {
      return {
        type: EncodingType.String,
        encoding: 'BOUNDED_PREFIX_LENGTH_8BIT_FIXED',
        options: {
          minimum: schema.minLength,
          maximum: schema.maxLength
        }
      }
    }

    return {
      type: EncodingType.String,
      encoding: 'FLOOR_PREFIX_LENGTH_ENUM_VARINT',
      options: {
        minimum: schema.minLength
      }
    }
  } else if ((typeof schema.minLength !== 'undefined' && schema.minLength > 0) && typeof schema.maxLength === 'undefined') {
    return {
      type: EncodingType.String,
      encoding: 'FLOOR_PREFIX_LENGTH_ENUM_VARINT',
      options: {
        minimum: schema.minLength
      }
    }
  } else if (typeof schema.minLength === 'undefined' && typeof schema.maxLength !== 'undefined') {
    if (schema.maxLength <= UINT8_MAX - 1) {
      return {
        type: EncodingType.String,
        encoding: 'BOUNDED_PREFIX_LENGTH_8BIT_FIXED',
        options: {
          minimum: 0,
          maximum: schema.maxLength
        }
      }
    }

    return {
      type: EncodingType.String,
      encoding: 'ROOF_PREFIX_LENGTH_ENUM_VARINT',
      options: {
        maximum: schema.maxLength
      }
    }
  }

  if (schema.contentMediaType === 'text/plain') {
    return {
      type: EncodingType.String,
      encoding: 'STRING_DICTIONARY_COMPRESSOR',

      // We should find a way to express the language of the text
      // using JSON Schema, so that we can pick the right dictionary
      // instead of always defaulting to english.
      options: ENGLISH_DICTIONARY
    }
  }

  return {
    type: EncodingType.String,
    encoding: 'FLOOR_PREFIX_LENGTH_ENUM_VARINT',
    options: {
      minimum: 0
    }
  }
}
