/*
 *  UVCCamera
 *  library and sample to access to UVC web camera on non-rooted Android device
 *
 * Copyright (c) 2014-2017 saki t_saki@serenegiant.com
 *
 *  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.
 *
 *  All files in the folder are under this Apache License, Version 2.0.
 *  Files in the libjpeg-turbo, libusb, libuvc, rapidjson folder
 *  may have a different license, see the respective files.
 */

package com.serenegiant.usb;

import android.os.Parcel;
import android.os.Parcelable;

import java.util.Locale;

public class Size implements Parcelable {
	//
	/**
	 * native側のuvc_raw_format_tの値, こっちは主にlibuvc用
	 * 9999 is still image
	 */
	public int type;
	/**
	 * native側のraw_frame_tの値, androusb用,
	 * libuvcは対応していない
	 */
	public int frame_type;
	public int index;
	public int width;
	public int height;
	public int frameIntervalType;
	public int frameIntervalIndex;
	public int[] intervals;
	// ここ以下はframeIntervalTypeとintervalsから#updateFrameRateで計算する
	public float[] fps;
	private String frameRates;

	/**
	 * コンストラクタ
	 * @param _type native側のraw_format_tの値, ただし9999は静止画
	 * @param _frame_type native側のraw_frame_tの値
	 * @param _index
	 * @param _width
	 * @param _height
	 */
	public Size(final int _type, final int _frame_type, final int _index, final int _width, final int _height) {
		type = _type;
		frame_type = _frame_type;
		index = _index;
		width = _width;
		height = _height;
		frameIntervalType = -1;
		frameIntervalIndex = 0;
		intervals = null;
		updateFrameRate();
	}

	/**
	 * コンストラクタ
	 * @param _type native側のraw_format_tの値, ただし9999は静止画
	 * @param _frame_type native側のraw_frame_tの値
	 * @param _index
	 * @param _width
	 * @param _height
	 * @param _min_intervals
	 * @param _max_intervals
	 */
	public Size(final int _type, final int _frame_type, final int _index, final int _width, final int _height, final int _min_intervals, final int _max_intervals, final int _step) {
		type = _type;
		frame_type = _frame_type;
		index = _index;
		width = _width;
		height = _height;
		frameIntervalType = 0;
		frameIntervalIndex = 0;
		intervals = new int[3];
		intervals[0] = _min_intervals;
		intervals[1] = _max_intervals;
		intervals[2] = _step;
		updateFrameRate();
	}

	/**
	 * コンストラクタ
	 * @param _type native側のraw_format_tの値, ただし9999は静止画
	 * @param _frame_type native側のraw_frame_tの値
	 * @param _index
	 * @param _width
	 * @param _height
     * @param _intervals
     */
	public Size(final int _type, final int _frame_type, final int _index, final int _width, final int _height, final int[] _intervals) {
		type = _type;
		frame_type = _frame_type;
		index = _index;
		width = _width;
		height = _height;
		final int n = _intervals != null ? _intervals.length : -1;
		if (n > 0) {
			frameIntervalType = n;
			intervals = new int[n];
			System.arraycopy(_intervals, 0, intervals, 0, n);
		} else {
			frameIntervalType = -1;
			intervals = null;
		}
		frameIntervalIndex = 0;
		updateFrameRate();
	}

	/**
	 * コピーコンストラクタ
	 * @param other
	 */
	public Size(final Size other) {
		type = other.type;
		frame_type = other.frame_type;
		index = other.index;
		width = other.width;
		height = other.height;
		frameIntervalType = other.frameIntervalType;
		frameIntervalIndex = other.frameIntervalIndex;
		final int n = other.intervals != null ? other.intervals.length : -1;
		if (n > 0) {
			intervals = new int[n];
			System.arraycopy(other.intervals, 0, intervals, 0, n);
		} else {
			intervals = null;
		}
		updateFrameRate();
	}

	private Size(final Parcel source) {
		// 読み取り順はwriteToParcelでの書き込み順と同じでないとダメ
		type = source.readInt();
		frame_type = source.readInt();
		index = source.readInt();
		width = source.readInt();
		height = source.readInt();
		frameIntervalType = source.readInt();
		frameIntervalIndex = source.readInt();
		if (frameIntervalType >= 0) {
			if (frameIntervalType > 0) {
				intervals = new int[frameIntervalType];
			} else {
				intervals = new int[3];
			}
			source.readIntArray(intervals);
		} else {
			intervals = null;
		}
		updateFrameRate();
	}

	public Size set(final Size other) {
		if (other != null) {
			type = other.type;
			frame_type = other.frame_type;
			index = other.index;
			width = other.width;
			height = other.height;
			frameIntervalType = other.frameIntervalType;
			frameIntervalIndex = other.frameIntervalIndex;
			final int n = other.intervals != null ? other.intervals.length : -1;
			if (n > 0) {
				intervals = new int[n];
				System.arraycopy(other.intervals, 0, intervals, 0, n);
			} else {
				intervals = null;
			}
			updateFrameRate();
		}
		return this;
	}

	public float getCurrentFrameRate() throws IllegalStateException {
		final int n = fps != null ? fps.length : 0;
		if ((frameIntervalIndex >= 0) && (frameIntervalIndex < n)) {
			return fps[frameIntervalIndex];
		}
		throw new IllegalStateException("unknown frame rate or not ready");
	}

	public void setCurrentFrameRate(final float frameRate) {
		// 一番近いのを選ぶ
		int index = -1;
		final int n = fps != null ? fps.length : 0;
		for (int i = 0; i < n; i++) {
			if (fps[i] <= frameRate) {
				index = i;
				break;
			}
		}
		frameIntervalIndex = index;
	}

	@Override
	public int describeContents() {
		return 0;
	}

	@Override
	public void writeToParcel(final Parcel dest, final int flags) {
		dest.writeInt(type);
		dest.writeInt(frame_type);
		dest.writeInt(index);
		dest.writeInt(width);
		dest.writeInt(height);
		dest.writeInt(frameIntervalType);
		dest.writeInt(frameIntervalIndex);
		if (intervals != null) {
			dest.writeIntArray(intervals);
		}
	}

	public void updateFrameRate() {
		final int n = frameIntervalType;
		if (n > 0) {
			fps = new float[n];
			for (int i = 0; i < n; i++) {
				final float _fps = fps[i] = 10000000.0f / intervals[i];
			}
		} else if (n == 0) {
			try {
				final int min = Math.min(intervals[0], intervals[1]);
				final int max = Math.max(intervals[0], intervals[1]);
				final int step = intervals[2];
				if (step > 0) {
					int m = 0;
					for (int i = min; i <= max; i+= step) { m++; }
					fps = new float[m];
					m = 0;
					for (int i = min; i <= max; i+= step) {
						final float _fps = fps[m++] = 10000000.0f / i;
					}
				} else {
					final float max_fps = 10000000.0f / min;
					int m = 0;
					for (float fps = 10000000.0f / min; fps <= max_fps; fps += 1.0f) { m++; }
					fps = new float[m];
					m = 0;
					for (float fps = 10000000.0f / min; fps <= max_fps; fps += 1.0f) {
						this.fps[m++] = fps;
					}
				}
			} catch (final Exception e) {
				// ignore, なんでかminとmaxが0になってるんちゃうかな
				fps = null;
			}
		}
		final int m = fps != null ? fps.length : 0;
		final StringBuilder sb = new StringBuilder();
		sb.append("[");
		for (int i = 0; i < m; i++) {
			sb.append(String.format(Locale.US, "%4.1f", fps[i]));
			if (i < m-1) {
				sb.append(",");
			}
		}
		sb.append("]");
		frameRates = sb.toString();
		if (frameIntervalIndex > m) {
			frameIntervalIndex = 0;
		}
	}

	@Override
	public String toString() {
		float frame_rate = 0.0f;
		try {
			frame_rate = getCurrentFrameRate();
		} catch (final Exception e) {
		}
		return String.format(Locale.US, "Size(%dx%d@%4.1f,type:%d,frame:%d,index:%d,%s)", width, height, frame_rate, type, frame_type, index, frameRates);
	}

	public static final Creator<Size> CREATOR = new Creator<Size>() {
		@Override
		public Size createFromParcel(final Parcel source) {
			return new Size(source);
		}
		@Override
		public Size[] newArray(final int size) {
			return new Size[size];
		}
	};
}
